From e2533d7ae61e55727b443b8493a6938dd2ba2598 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 22 Sep 2023 16:25:37 -0400 Subject: [PATCH 0001/2524] initial commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..70653ec5 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# random number generators From fadbcd7b54bd689d4bd31026a51c956ea781044c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 23 Sep 2023 13:07:45 -0400 Subject: [PATCH 0002/2524] + xoshiro256ss (copied from kalman project) --- CMakeLists.txt | 73 +++ README.md | 15 + cmake/code-coverage.cmake | 678 +++++++++++++++++++++++++++ cmake/cxx.cmake | 40 ++ example/CMakeLists.txt | 2 + example/ex1/CMakeLists.txt | 2 + example/ex1/ex1.cpp | 26 + example/ex2/CMakeLists.txt | 3 + example/ex2/ex2.cpp | 16 + include/randomgen/engine_concept.hpp | 36 ++ include/randomgen/random_seed.hpp | 70 +++ include/randomgen/xoshiro256.hpp | 169 +++++++ 12 files changed, 1130 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/code-coverage.cmake create mode 100644 cmake/cxx.cmake create mode 100644 example/CMakeLists.txt create mode 100644 example/ex1/CMakeLists.txt create mode 100644 example/ex1/ex1.cpp create mode 100644 example/ex2/CMakeLists.txt create mode 100644 example/ex2/ex2.cpp create mode 100644 include/randomgen/engine_concept.hpp create mode 100644 include/randomgen/random_seed.hpp create mode 100644 include/randomgen/xoshiro256.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..533fe8e5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,73 @@ +# using indentlog/CMakeLists.txt as model + +cmake_minimum_required(VERSION 3.10) + +project(randomgen 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. +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) + +# ---------------------------------------------------------------- +# c++ settings + +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 "") + +# ---------------------------------------------------------------- +# default install + +if(NOT USER) + set(USER $ENV{USER}) +endif() + +if(NOT CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") +endif() +if(NOT CMAKE_INSTALL_RPATH) + set(CMAKE_INSTALL_RPATH /home/${USER}/local/lib CACHE STRING "runpath in installed libraries/executables") +endif() + +# ---------------------------------------------------------------- +# external dependencies +# +# set CMAKE_INSTALL_PREFIX to analog of /usr +# to use .cmake assistants from /usr/lib/cmake/indentlog +# +find_package(indentlog REQUIRED) + +# ---------------------------------------------------------------- + +add_subdirectory(example) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- + +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) + +install(TARGETS ex1 DESTINATION bin/randomgen/example) diff --git a/README.md b/README.md index 70653ec5..18300542 100644 --- a/README.md +++ b/README.md @@ -1 +1,16 @@ # random number generators + +# to build + install locally + +``` +$ cd randomgen +$ mkdir build +$ cd build +$ cmake -DCMAKE_PREFIX_PATH=$(HOME)/local .. +$ make +$ make install +``` + +# to build + install to /usr/local (deprecated) + +same as above, but set `CMAKE_PREFIX_PATH` to `/usr/local` diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake new file mode 100644 index 00000000..b6b36064 --- /dev/null +++ b/cmake/code-coverage.cmake @@ -0,0 +1,678 @@ +# +# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# USAGE: To enable any code coverage instrumentation/targets, the single CMake +# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or +# on the command line. +# +# From this point, there are two primary methods for adding instrumentation to +# targets: +# +# 1 - A blanket instrumentation by calling `add_code_coverage()`, where +# all targets in that directory and all subdirectories are automatically +# instrumented. +# +# 2 - Per-target instrumentation by calling +# `target_code_coverage()`, where the target is given and thus only +# that target is instrumented. This applies to both libraries and executables. +# +# To add coverage targets, such as calling `make ccov` to generate the actual +# coverage information for perusal or consumption, call +# `target_code_coverage()` on an *executable* target. +# +# Example 1: All targets instrumented +# +# In this case, the coverage information reported will will be that of the +# `theLib` library target and `theExe` executable. +# +# 1a: Via global command +# +# ~~~ +# add_code_coverage() # Adds instrumentation to all targets +# +# add_library(theLib lib.cpp) +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target +# # (instrumentation already added via global anyways) +# # for generating code coverage reports. +# ~~~ +# +# 1b: Via target commands +# +# ~~~ +# add_library(theLib lib.cpp) +# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. +# ~~~ +# +# Example 2: Target instrumented, but with regex pattern of files to be excluded +# from report +# +# ~~~ +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ +# +# Example 3: Target added to the 'ccov' and 'ccov-all' targets +# +# ~~~ +# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. +# +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ + +# Options +option( + CODE_COVERAGE + "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" + OFF) + +# Programs +find_program(LLVM_COV_PATH llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) +# Hide behind the 'advanced' mode flag for GUI/ccmake +mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) + +# Variables +set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) +set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) + +# Common initialization/checks +if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) + set(CODE_COVERAGE_ADDED ON) + + # Common Targets + add_custom_target( + ccov-preprocessing + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} + DEPENDS ccov-clean) + + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + # Messages + message(STATUS "Building with llvm Code Coverage Tools") + + if(NOT LLVM_COV_PATH) + message(FATAL_ERROR "llvm-cov not found! Aborting.") + else() + # Version number checking for 'EXCLUDE' compatibility + execute_process(COMMAND ${LLVM_COV_PATH} --version + OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION + ${LLVM_COV_VERSION_CALL_OUTPUT}) + + if(LLVM_COV_VERSION VERSION_LESS "7.0.0") + message( + WARNING + "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" + ) + endif() + endif() + + # Targets + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + else() + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + endif() + + # Used to get the shared object file list before doing the main all- + # processing + add_custom_target( + ccov-libs + COMMAND ; + COMMENT "libs ready for coverage report.") + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + # Messages + message(STATUS "Building with lcov Code Coverage Tools") + + if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) + if(NOT ${upper_build_type} STREQUAL "DEBUG") + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + else() + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Targets + add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory + ${CMAKE_BINARY_DIR} --zerocounters) + + else() + message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") + endif() +endif() + +# Adds code coverage instrumentation to a library, or instrumentation/targets +# for an executable target. +# ~~~ +# EXECUTABLE ADDED TARGETS: +# GCOV/LCOV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# +# LLVM-COV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report. +# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. +# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. +# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. +# ccov-all-export : Exports the coverage report to a JSON file. +# +# Required: +# TARGET_NAME - Name of the target to generate code coverage for. +# Optional: +# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. +# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. +# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) +# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. +# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. +# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory +# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** +# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call +# ~~~ +function(target_code_coverage TARGET_NAME) + # Argument parsing + set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) + set(single_value_keywords COVERAGE_TARGET_NAME) + set(multi_value_keywords EXCLUDE OBJECTS ARGS) + cmake_parse_arguments( + target_code_coverage "${options}" "${single_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + # Set the visibility of target functions to PUBLIC, INTERFACE or default to + # PRIVATE. + if(target_code_coverage_PUBLIC) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY PUBLIC) + elseif(target_code_coverage_INTERFACE) + set(TARGET_VISIBILITY INTERFACE) + set(TARGET_LINK_VISIBILITY INTERFACE) + elseif(target_code_coverage_PLAIN) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY) + else() + set(TARGET_VISIBILITY PRIVATE) + set(TARGET_LINK_VISIBILITY PRIVATE) + endif() + + if(NOT target_code_coverage_COVERAGE_TARGET_NAME) + # If a specific name was given, use that instead. + set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) + endif() + + if(CODE_COVERAGE) + + # Add code coverage instrumentation to the target's linker command + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs + -ftest-coverage) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) + endif() + + # Targets + get_target_property(target_type ${TARGET_NAME} TYPE) + + # Add shared library to processing for 'all' targets + if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" >> + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + if(NOT TARGET ccov-libs) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-libs + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # If there are shared objects to also work with, generate the string to + # add them here + foreach(SO_TARGET ${target_code_coverage_OBJECTS}) + # Check to see if the target is a shared object + if(TARGET ${SO_TARGET}) + get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) + if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") + set(SO_OBJECTS ${SO_OBJECTS} -object=$) + endif() + endif() + endforeach() + + # Run the executable, generating raw profile data Make the run data + # available for further processing. Separated to allow Windows to run + # this target serially. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list + JOB_POOL ccov_serial_pool + DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) + + # Merge the generated profile data so llvm-cov can process it + add_custom_target( + ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_PROFDATA_PATH} merge -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Ignore regex only works on LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print out details of the coverage information to the command line + add_custom_target( + ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Print out a summary of the coverage information to the command line + add_custom_target( + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) + + # Run the executable, generating coverage information + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND $ ${target_code_coverage_ARGS} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + # Generate exclusion string for use + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + if(NOT ${target_code_coverage_EXTERNAL}) + set(EXTERNAL_OPTION --no-external) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + else() + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + endif() + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + + add_custom_command( + TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." + ) + + # AUTO + if(target_code_coverage_AUTO) + if(NOT TARGET ccov) + add_custom_target(ccov) + endif() + add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) + + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) + endif() + add_dependencies( + ccov-report + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # ALL + if(target_code_coverage_ALL) + if(NOT TARGET ccov-all-processing) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-all-processing + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + endif() +endfunction() + +# Adds code coverage instrumentation to all targets in the current directory and +# any subdirectories. To add coverage instrumentation to only specific targets, +# use `target_code_coverage`. +function(add_code_coverage) + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_compile_options(-fprofile-instr-generate -fcoverage-mapping) + add_link_options(-fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options(-fprofile-arcs -ftest-coverage) + link_libraries(gcov) + endif() + endif() +endfunction() + +# Adds the 'ccov-all' type targets that calls all targets added via +# `target_code_coverage` with the `ALL` parameter, but merges all the coverage +# data from them into a single large report instead of the numerous smaller +# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for +# use with coverage dashboards (e.g. codecov.io, coveralls). +# ~~~ +# Optional: +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! +# ~~~ +function(add_code_coverage_all_targets) + # Argument parsing + set(multi_value_keywords EXCLUDE) + cmake_parse_arguments(add_code_coverage_all_targets "" "" + "${multi_value_keywords}" ${ARGN}) + + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # Merge the profile data for all of the run executables + if(WIN32) + add_custom_target( + ccov-all-processing + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe + merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -sparse $$FILELIST) + else() + add_custom_target( + ccov-all-processing + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + endif() + + # Regex exclude only available for LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print summary of the code coverage information to the command line + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-all-export + COMMAND + ${LLVM_COV_PATH} export `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + + # Generate HTML output of all added targets for perusal + if(WIN32) + add_custom_target( + ccov-all + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") + + # Nothing required for gcov + add_custom_target(ccov-all-processing COMMAND ;) + + # Exclusion regex string creation + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + else() + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + endif() + + # Generates HTML output of all targets for perusal + add_custom_target( + ccov-all + COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} + DEPENDS ccov-all-capture) + + endif() + + add_custom_command( + TARGET ccov-all + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." + ) + endif() +endfunction() diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake new file mode 100644 index 00000000..e01e6801 --- /dev/null +++ b/cmake/cxx.cmake @@ -0,0 +1,40 @@ +# ---------------------------------------------------------------- +# 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 + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_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() +endmacro() + +# ---------------------------------------------------------------- +# use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers +# +macro(xo_indentlog_dependency target) + find_package(indentlog REQUIRED) + #add_dependencies(${target} indentlog) + target_link_libraries(${target} PUBLIC indentlog) + #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) +endmacro() diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 00000000..ac5b07f6 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(ex1) +add_subdirectory(ex2) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..6af29d79 --- /dev/null +++ b/example/ex1/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(ex1 ex1.cpp) +xo_include_options(ex1) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp new file mode 100644 index 00000000..8c908fa5 --- /dev/null +++ b/example/ex1/ex1.cpp @@ -0,0 +1,26 @@ +/* @file ex1.cpp */ + +#include "randomgen/xoshiro256.hpp" +#include +#include +//#include +//#include + +using namespace xo; +using namespace xo::rng; + +int +main(int argc, char ** argv) { + xoshiro256ss rng{123456789}; + + std::array v; + + std::generate(v.begin(), v.end(), rng); + + for (std::uint64_t i=0; i seed; + + xoshiro256ss eng(seed); +} /*main*/ + +/* end ex2.cpp */ diff --git a/include/randomgen/engine_concept.hpp b/include/randomgen/engine_concept.hpp new file mode 100644 index 00000000..42557b0e --- /dev/null +++ b/include/randomgen/engine_concept.hpp @@ -0,0 +1,36 @@ +/* @file engine_concept.hpp */ + +#pragma once + +#include +#include + +namespace xo { + namespace rng { + /* an engine generates psuedo-random bits. + * given + * RngEngine eng = ...; + * + * RngEngine::result_type x = eng(); + * + * puts random bits into x. + */ + template + concept engine_concept = requires(RngEngine engine, typename RngEngine::result_type r) { + /* note: the first 4 requirements characterize UniformRandomBitGenerator */ + typename RngEngine::result_type; + { RngEngine(r) }; + { engine.min() } -> std::same_as; + { engine.max() } -> std::same_as; + /* must return value in closed interval [.min(), .max()] */ + { engine() } -> std::same_as; + + { engine.seed() }; + { engine.seed(r) }; + { engine == engine }; + { engine != engine }; + } && std::copyable && std::uniform_random_bit_generator; + } /*namespace rng*/ +} /*namespace xo*/ + +/* end engine_concept.hpp */ diff --git a/include/randomgen/random_seed.hpp b/include/randomgen/random_seed.hpp new file mode 100644 index 00000000..5273ddeb --- /dev/null +++ b/include/randomgen/random_seed.hpp @@ -0,0 +1,70 @@ +/* @file random_seed.hpp */ + +#include "indentlog/print/array.hpp" +#include +#include +#include + +namespace xo { + namespace rng { + /* generate a 64-bit random seed using /dev/urandom or similar source. + * This is relatively expensive; at least cost of a system call + * + may block if host has rebooted recently + * + * Require: + * - T is null-constructible. + * + * return value will contain a T-instance in which representation + * has been populated with random bits. Expecting T to be something + * like int32_t, or std::array + */ + template + void random_seed(T * p_seed) { + /* NOTE: arc4random_buf() works on darwin/nix; + * probably need to do something else on intel linux + */ + arc4random_buf(p_seed, sizeof(*p_seed)); + } /*random_seed*/ + + template + T random_seed() { + T retval; + random_seed(&retval); + + return retval; + } /*random_seed*/ + + /* RAII-style random-number seed + * + * Usage: + * + * Seed seed; + * + * auto eng = xoshiro256ss(seed); + * or + * auto rng = UnitIntervalGen::make(seed); + */ + template + struct Seed { + using seed_type = typename Engine::seed_type; + + Seed() { random_seed(&seed_); } + + operator seed_type const & () const { return seed_; } + + seed_type seed_; + }; /*Seed*/ + + template + inline std::ostream & + operator<<(std::ostream & os, + Seed const & x) + { + os << x.seed_; + return os; + } /*operator<<*/ + + } /*namespace rng*/ +} /*namespace xo*/ + +/* end random_seed.hpp */ diff --git a/include/randomgen/xoshiro256.hpp b/include/randomgen/xoshiro256.hpp new file mode 100644 index 00000000..b202650a --- /dev/null +++ b/include/randomgen/xoshiro256.hpp @@ -0,0 +1,169 @@ +/* @file xoshiro256.hpp */ + +#pragma once + +#include "engine_concept.hpp" +#include +#include +#include +#include + +namespace xo { + namespace rng { + + /* engine for producing 64-bit random numbers + * + * see https:/en.wikipedia.org/wiki/Xorshift#xoshiro256** + * + * - satisfies c++ UniformRandomBitGenerator + * - satisfies c++ + * + * Note: zero seed --> constant output sequence {0, 0, 0, ...} + */ + class xoshiro256ss { + public: + using result_type = std::uint64_t; + using seed_type = std::array; + + public: + /* null state -- generates constant stream of 0 bits */ + xoshiro256ss() : xoshiro256ss(0) {} + /* copy ctor */ + xoshiro256ss(xoshiro256ss const & x) = default; + xoshiro256ss(seed_type const & seed) : s_(seed) {} + + /* fallback version -- deprecated */ + xoshiro256ss(std::uint64_t seed) + { + this->s_[0] = 0; + this->s_[1] = seed; + this->s_[2] = 0; + this->s_[3] = 0; + + generate(); + } + + static constexpr std::uint64_t min() { return 0; } + static constexpr std::uint64_t max() { return std::numeric_limits::max(); } + + static std::uint64_t rol64(std::uint64_t x, std::int64_t k) + { + return (x << k) | (x >> (64 - k)); + } + + static bool equal(xoshiro256ss const & x, xoshiro256ss const & y) { + return ((x.s_[0] == y.s_[0]) + && (x.s_[1] == y.s_[1]) + && (x.s_[2] == y.s_[2]) + && (x.s_[3] == y.s_[3])); + } + + /* puts generator into null state */ + void seed() { *this = xoshiro256ss(); } + void seed(std::uint64_t s) { *this = xoshiro256ss{s}; } + /* e.g. used with std::seed_seq<> */ + template + void seed(SeedSeq & sseq) { + sseq.generate(s_.begin(), s_.end()); + } + + std::uint64_t generate() { + std::array & s = (this->s_); + std::uint64_t const result = rol64(s[1] * 5, 7) * 9; + std::uint64_t const t = s[1] << 17; + + s[2] ^= s[0]; + s[3] ^= s[1]; + s[1] ^= s[2]; + s[0] ^= s[3]; + + s[2] ^= t; + s[3] = rol64(s[3], 45); + + return result; + } /*generate*/ + + /* advance to same state as obtained from z calls to .generate(). O(z) ! + * usually better to use jump(). + * + * providing .discard() to satisfy c++ named requirement _RandomNumberEngine_ + */ + void discard(std::uint64_t z) { + for (std::uint64_t i=0; igenerate(); + } + + /* equivalent to .discard(2^128), but uses O(1) time + * + * (may use in multithreaded program to get determinsitic non-overlapping random sequences) + */ + void jump() { + std::array const s_jump_v + = {{0x180ec6d33cfd0aba, + 0xd5a61266f0c9392c, + 0xa9582618e03fc9aa, + 0x39abdc4529b1661c}}; + + std::array & s = (this->s_); + + std::uint64_t s0 = 0; + std::uint64_t s1 = 0; + std::uint64_t s2 = 0; + std::uint64_t s3 = 0; + for (std::uint32_t i = 0; i < s_jump_v.size(); ++i) { + for (std::uint32_t bit = 0; bit < 64; ++bit) { + if (s_jump_v[i] & 1UL << bit) { + s0 ^= s[0]; + s1 ^= s[1]; + s2 ^= s[2]; + s3 ^= s[3]; + } + this->generate(); + } + } + + s[0] = s0; + s[1] = s1; + s[2] = s2; + s[3] = s3; + } /*jump*/ + + /* inverse of .load() */ + void print(std::ostream & os) const { + os << ""; + } + + /* inverse of .print() */ + void load(std::istream & is) { + std::string header, trailer; + std::array sv; + + is >> header >> sv[0] >> sv[1] >> sv[2] >> sv[3] >> trailer; + + if ((header != ""); + + this->s_ = sv; + } /*load*/ + + std::uint64_t operator()() { return generate(); } + + private: + /* state */ + std::array s_; + }; /*xoshiro256ss*/ + + inline bool operator==(xoshiro256ss const & x, xoshiro256ss const & y) { + return xoshiro256ss::equal(x, y); + } + + inline bool operator!=(xoshiro256ss const & x, xoshiro256ss const & y) { + return !xoshiro256ss::equal(x, y); + } + + static_assert(engine_concept); + + } /*namespace rng*/ +} /*namespace xo*/ + +/* end xoshiro256.hpp */ From 1db6cd4d4a267320502f64f6e941a5990d221ede Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 23 Sep 2023 13:14:50 -0400 Subject: [PATCH 0003/2524] build: fix CMAKE_INSTALL_RPATH --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 533fe8e5..f53843ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,7 @@ if(NOT CMAKE_INSTALL_PREFIX) set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") endif() if(NOT CMAKE_INSTALL_RPATH) - set(CMAKE_INSTALL_RPATH /home/${USER}/local/lib CACHE STRING "runpath in installed libraries/executables") + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath in installed libraries/executables") endif() # ---------------------------------------------------------------- From 0e18026fba8eab1b646488770c02001fad7b08b7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 23 Sep 2023 15:47:31 -0400 Subject: [PATCH 0004/2524] initial commit --- README.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..0835e7b9 --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# intrusive reference counting + +Refcnt is a small shared library supplying intrusive reference counting. + +## Features + +- base class `ref::Refcounted`. + Application classes opt-in to reference counting by inheriting this class. +- common base simplifies connecting to common-base-object applications such as python, java etc. + +## Getting Started + +### build + install `indentlog` dependency + +see [github/rconybea/indentlog](https://github.com/Rconybea/indentlog) + +### copy `refcnt` repository locally +``` +$ git clone git@github.com:rconybea/refcnt.git +$ ls -d refcnt +refcnt +``` + +### build + install +``` +$ cd refcnt +$ mkdir build +$ cd build +$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` + +alternatively, if you're a nix user: +``` +$ git clone git@github.com:rconybea/xo-nix.git +$ ls -d xo-nix +xo-nix +$ cd xo-nix +$ nix-build -A refcnt +``` + +## Examples + +### 1 +``` +#include "refcnt/Refcounted.hpp" + +using xo::ref::Refcounted; + +struct MyObject : public Refcounted { + static rp make() { return new MyObject(); } + +private: + // intrusively-reference-counted objects should only be heap-allocated + MyObject() { ... } +}; + +int main() { + // create reference-counted instance + auto x = MyObject::make(); + auto y = x; + // x,y refer to the same instance. + x = nullptr; + // y holds last reference + y = nullptr; + // MyObject has been deleted +} +``` + +### 2 + +To log reference-counting activity + +``` +xo::ref::intrusive_ptr_set_debug(true); +``` From 87b4bfa7953f254e716611f8a30e4679a39bfd54 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 23 Sep 2023 15:48:36 -0400 Subject: [PATCH 0005/2524] + implementation --- CMakeLists.txt | 52 +++ cmake/code-coverage.cmake | 678 +++++++++++++++++++++++++++++++++ cmake/cxx.cmake | 86 +++++ include/cxxutil/demangle.hpp | 92 +++++ include/refcnt/Displayable.hpp | 29 ++ include/refcnt/Refcounted.hpp | 294 ++++++++++++++ include/refcnt/Unowned.hpp | 28 ++ src/CMakeLists.txt | 14 + src/Displayable.cpp | 16 + src/Refcounted.cpp | 157 ++++++++ 10 files changed, 1446 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/code-coverage.cmake create mode 100644 cmake/cxx.cmake create mode 100644 include/cxxutil/demangle.hpp create mode 100644 include/refcnt/Displayable.hpp create mode 100644 include/refcnt/Refcounted.hpp create mode 100644 include/refcnt/Unowned.hpp create mode 100644 src/CMakeLists.txt create mode 100644 src/Displayable.cpp create mode 100644 src/Refcounted.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..1e8053f9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +# refcnt/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo-refcnt 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. +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) + +# ---------------------------------------------------------------- +# c++ settings + +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 "") + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src) + +# ---------------------------------------------------------------- +# install .hpp + +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) + +# end CMakeLists.txt diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake new file mode 100644 index 00000000..b6b36064 --- /dev/null +++ b/cmake/code-coverage.cmake @@ -0,0 +1,678 @@ +# +# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# USAGE: To enable any code coverage instrumentation/targets, the single CMake +# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or +# on the command line. +# +# From this point, there are two primary methods for adding instrumentation to +# targets: +# +# 1 - A blanket instrumentation by calling `add_code_coverage()`, where +# all targets in that directory and all subdirectories are automatically +# instrumented. +# +# 2 - Per-target instrumentation by calling +# `target_code_coverage()`, where the target is given and thus only +# that target is instrumented. This applies to both libraries and executables. +# +# To add coverage targets, such as calling `make ccov` to generate the actual +# coverage information for perusal or consumption, call +# `target_code_coverage()` on an *executable* target. +# +# Example 1: All targets instrumented +# +# In this case, the coverage information reported will will be that of the +# `theLib` library target and `theExe` executable. +# +# 1a: Via global command +# +# ~~~ +# add_code_coverage() # Adds instrumentation to all targets +# +# add_library(theLib lib.cpp) +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target +# # (instrumentation already added via global anyways) +# # for generating code coverage reports. +# ~~~ +# +# 1b: Via target commands +# +# ~~~ +# add_library(theLib lib.cpp) +# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. +# ~~~ +# +# Example 2: Target instrumented, but with regex pattern of files to be excluded +# from report +# +# ~~~ +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ +# +# Example 3: Target added to the 'ccov' and 'ccov-all' targets +# +# ~~~ +# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. +# +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ + +# Options +option( + CODE_COVERAGE + "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" + OFF) + +# Programs +find_program(LLVM_COV_PATH llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) +# Hide behind the 'advanced' mode flag for GUI/ccmake +mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) + +# Variables +set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) +set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) + +# Common initialization/checks +if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) + set(CODE_COVERAGE_ADDED ON) + + # Common Targets + add_custom_target( + ccov-preprocessing + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} + DEPENDS ccov-clean) + + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + # Messages + message(STATUS "Building with llvm Code Coverage Tools") + + if(NOT LLVM_COV_PATH) + message(FATAL_ERROR "llvm-cov not found! Aborting.") + else() + # Version number checking for 'EXCLUDE' compatibility + execute_process(COMMAND ${LLVM_COV_PATH} --version + OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION + ${LLVM_COV_VERSION_CALL_OUTPUT}) + + if(LLVM_COV_VERSION VERSION_LESS "7.0.0") + message( + WARNING + "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" + ) + endif() + endif() + + # Targets + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + else() + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + endif() + + # Used to get the shared object file list before doing the main all- + # processing + add_custom_target( + ccov-libs + COMMAND ; + COMMENT "libs ready for coverage report.") + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + # Messages + message(STATUS "Building with lcov Code Coverage Tools") + + if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) + if(NOT ${upper_build_type} STREQUAL "DEBUG") + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + else() + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Targets + add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory + ${CMAKE_BINARY_DIR} --zerocounters) + + else() + message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") + endif() +endif() + +# Adds code coverage instrumentation to a library, or instrumentation/targets +# for an executable target. +# ~~~ +# EXECUTABLE ADDED TARGETS: +# GCOV/LCOV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# +# LLVM-COV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report. +# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. +# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. +# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. +# ccov-all-export : Exports the coverage report to a JSON file. +# +# Required: +# TARGET_NAME - Name of the target to generate code coverage for. +# Optional: +# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. +# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. +# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) +# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. +# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. +# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory +# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** +# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call +# ~~~ +function(target_code_coverage TARGET_NAME) + # Argument parsing + set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) + set(single_value_keywords COVERAGE_TARGET_NAME) + set(multi_value_keywords EXCLUDE OBJECTS ARGS) + cmake_parse_arguments( + target_code_coverage "${options}" "${single_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + # Set the visibility of target functions to PUBLIC, INTERFACE or default to + # PRIVATE. + if(target_code_coverage_PUBLIC) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY PUBLIC) + elseif(target_code_coverage_INTERFACE) + set(TARGET_VISIBILITY INTERFACE) + set(TARGET_LINK_VISIBILITY INTERFACE) + elseif(target_code_coverage_PLAIN) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY) + else() + set(TARGET_VISIBILITY PRIVATE) + set(TARGET_LINK_VISIBILITY PRIVATE) + endif() + + if(NOT target_code_coverage_COVERAGE_TARGET_NAME) + # If a specific name was given, use that instead. + set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) + endif() + + if(CODE_COVERAGE) + + # Add code coverage instrumentation to the target's linker command + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs + -ftest-coverage) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) + endif() + + # Targets + get_target_property(target_type ${TARGET_NAME} TYPE) + + # Add shared library to processing for 'all' targets + if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" >> + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + if(NOT TARGET ccov-libs) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-libs + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # If there are shared objects to also work with, generate the string to + # add them here + foreach(SO_TARGET ${target_code_coverage_OBJECTS}) + # Check to see if the target is a shared object + if(TARGET ${SO_TARGET}) + get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) + if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") + set(SO_OBJECTS ${SO_OBJECTS} -object=$) + endif() + endif() + endforeach() + + # Run the executable, generating raw profile data Make the run data + # available for further processing. Separated to allow Windows to run + # this target serially. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list + JOB_POOL ccov_serial_pool + DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) + + # Merge the generated profile data so llvm-cov can process it + add_custom_target( + ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_PROFDATA_PATH} merge -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Ignore regex only works on LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print out details of the coverage information to the command line + add_custom_target( + ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Print out a summary of the coverage information to the command line + add_custom_target( + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) + + # Run the executable, generating coverage information + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND $ ${target_code_coverage_ARGS} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + # Generate exclusion string for use + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + if(NOT ${target_code_coverage_EXTERNAL}) + set(EXTERNAL_OPTION --no-external) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + else() + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + endif() + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + + add_custom_command( + TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." + ) + + # AUTO + if(target_code_coverage_AUTO) + if(NOT TARGET ccov) + add_custom_target(ccov) + endif() + add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) + + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) + endif() + add_dependencies( + ccov-report + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # ALL + if(target_code_coverage_ALL) + if(NOT TARGET ccov-all-processing) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-all-processing + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + endif() +endfunction() + +# Adds code coverage instrumentation to all targets in the current directory and +# any subdirectories. To add coverage instrumentation to only specific targets, +# use `target_code_coverage`. +function(add_code_coverage) + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_compile_options(-fprofile-instr-generate -fcoverage-mapping) + add_link_options(-fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options(-fprofile-arcs -ftest-coverage) + link_libraries(gcov) + endif() + endif() +endfunction() + +# Adds the 'ccov-all' type targets that calls all targets added via +# `target_code_coverage` with the `ALL` parameter, but merges all the coverage +# data from them into a single large report instead of the numerous smaller +# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for +# use with coverage dashboards (e.g. codecov.io, coveralls). +# ~~~ +# Optional: +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! +# ~~~ +function(add_code_coverage_all_targets) + # Argument parsing + set(multi_value_keywords EXCLUDE) + cmake_parse_arguments(add_code_coverage_all_targets "" "" + "${multi_value_keywords}" ${ARGN}) + + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # Merge the profile data for all of the run executables + if(WIN32) + add_custom_target( + ccov-all-processing + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe + merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -sparse $$FILELIST) + else() + add_custom_target( + ccov-all-processing + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + endif() + + # Regex exclude only available for LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print summary of the code coverage information to the command line + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-all-export + COMMAND + ${LLVM_COV_PATH} export `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + + # Generate HTML output of all added targets for perusal + if(WIN32) + add_custom_target( + ccov-all + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") + + # Nothing required for gcov + add_custom_target(ccov-all-processing COMMAND ;) + + # Exclusion regex string creation + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + else() + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + endif() + + # Generates HTML output of all targets for perusal + add_custom_target( + ccov-all + COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} + DEPENDS ccov-all-capture) + + endif() + + add_custom_command( + TARGET ccov-all + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." + ) + endif() +endfunction() diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake new file mode 100644 index 00000000..9fc84e60 --- /dev/null +++ b/cmake/cxx.cmake @@ -0,0 +1,86 @@ +# ---------------------------------------------------------------- +# 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 + ${PROJECT_SOURCE_DIR}/include # e.g. for #include "indentlog/scope.hpp" + ${PROJECT_SOURCE_DIR}/include/${target} # e.g. for #include "Refcounted.hpp" in refcnt/src + ${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 :460: +# :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] +# #define _FORTIFY_SOURCE 2 +# +set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) + +# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro +if(XO_ADDRESS_SANITIZE) + set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) +else() + set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) +endif() + +# ---------------------------------------------------------------- +# generally want all the errors+warnings! +# however: address sanitizer generates error on _FORTIFY_SOURCE +# +macro(xo_compile_options target) + target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) +endmacro() + +# ---------------------------------------------------------------- +# use this for a subdir that builds a library +# +macro(xo_install_library target) + install(TARGETS ${target} DESTINATION lib) +endmacro() + +# ---------------------------------------------------------------- +# use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers +# +macro(xo_indentlog_dependency target) + find_package(indentlog REQUIRED) + #add_dependencies(${target} indentlog) + target_link_libraries(${target} PUBLIC indentlog) + #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) +endmacro() diff --git a/include/cxxutil/demangle.hpp b/include/cxxutil/demangle.hpp new file mode 100644 index 00000000..8b8b8ba3 --- /dev/null +++ b/include/cxxutil/demangle.hpp @@ -0,0 +1,92 @@ +/* @file demangle.hpp */ + +#pragma once + +#include +#include +#include // std::array +#include // std::index_sequence + +namespace xo { + namespace reflect { + + template + constexpr auto + substring_as_array(std::string_view str, + std::index_sequence indexes) + { + //return std::array{str[Idxs]..., '\n'}; + return std::array{str[Idxs]...}; + } /*substring_as_array*/ + + template constexpr auto type_name_array() { +#if defined(__clang__) + constexpr auto prefix = std::string_view{"[T = "}; + constexpr auto suffix = std::string_view{"]"}; + constexpr auto function = std::string_view{__PRETTY_FUNCTION__}; +#elif defined(__GNUC__) + constexpr auto prefix = std::string_view{"with T = "}; + constexpr auto suffix = std::string_view{"]"}; + constexpr auto function = std::string_view{__PRETTY_FUNCTION__}; +#elif defined(_MSC_VER) + constexpr auto prefix = std::string_view{"type_name_array<"}; + constexpr auto suffix = std::string_view{">(void)"}; + constexpr auto function = std::string_view{__FUNCSIG__}; +#else +# error type_name_array: Unsupported compiler +#endif + + constexpr auto start = function.find(prefix) + prefix.size(); + constexpr auto end = function.rfind(suffix); + + //static_assert(start < end); + + constexpr auto name = function.substr(start, (end - start)); + + constexpr auto ixseq = std::make_index_sequence{}; + + return substring_as_array(name, ixseq); + } /*type_name_array*/ + + template + struct type_name_holder { + static inline constexpr auto value = type_name_array(); + }; + + template + constexpr auto type_name() -> std::string_view + { + constexpr auto& value = type_name_holder::value; + return std::string_view{value.data(), value.size()}; + } + +#ifdef NOT_IN_USE + template + struct join + { + // Join all strings into a single std::array of chars + static constexpr auto impl() noexcept + { + constexpr std::size_t len = (Strs.size() + ... + 0); + std::array arr{}; + auto append = [i = 0, &arr](auto const& s) mutable { + for (auto c : s) arr[i++] = c; + }; + (append(Strs), ...); + arr[len] = 0; + return arr; + } + // Give the joined string static storage + static constexpr auto arr = impl(); + // View as a std::string_view + static constexpr std::string_view value {arr.data(), arr.size() - 1}; + }; + + // Helper to get the value out + template + static constexpr auto join_v = join::value; +#endif + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end demangle.hpp */ diff --git a/include/refcnt/Displayable.hpp b/include/refcnt/Displayable.hpp new file mode 100644 index 00000000..74708573 --- /dev/null +++ b/include/refcnt/Displayable.hpp @@ -0,0 +1,29 @@ +/* @file Displayable.hpp */ + +#pragma once + +#include "refcnt/Refcounted.hpp" + +namespace xo { + namespace ref { + class Displayable : public Refcount { + public: + /* write some kind of human-readable representation on stream */ + virtual void display(std::ostream & os) const = 0; + std::string display_string() const; + }; /*Displayable*/ + + /* see also + * operator<<(std::ostream &, intrusive_ptr const &) + * in [Refcounted.hpp] + */ + inline std::ostream & + operator<<(std::ostream &os, Displayable const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace ref*/ +} /*namespace xo*/ + +/* end Displayable.hpp */ diff --git a/include/refcnt/Refcounted.hpp b/include/refcnt/Refcounted.hpp new file mode 100644 index 00000000..17d778c7 --- /dev/null +++ b/include/refcnt/Refcounted.hpp @@ -0,0 +1,294 @@ +/* @file Refcounted.hpp */ + +#pragma once + +#include "indentlog/scope.hpp" +#include "cxxutil/demangle.hpp" + +//#include +#include +#include + +namespace xo { + namespace ref { + class Refcount; + + template + class Borrow; + + /* originally used boost::instrusive_ptr<>. + * ran into a bug. probably mine, but implemented + * refcounting inline for debugging + */ + template + class intrusive_ptr { + public: + using element_type = T; + + public: + intrusive_ptr() : ptr_(nullptr) {} + intrusive_ptr(T * x) : ptr_(x) { + intrusive_ptr_log_ctor(sc_self_type, this, x); + intrusive_ptr_add_ref(ptr_); + } /*ctor*/ + + /* NOTE: need exactly this form for copy-constructor + * clang11 will not recognize template form below as + * supplying copy ctor, and default version is broken for + * instrusive_ptr. + */ + intrusive_ptr(intrusive_ptr const & x) : ptr_(x.get()) { + intrusive_ptr_log_cctor(sc_self_type, this, x.get()); + intrusive_ptr_add_ref(ptr_); + } /*cctor*/ + + /* create from instrusive pointer to some related type S */ + template + intrusive_ptr(intrusive_ptr const & x) : ptr_(x.get()) { + intrusive_ptr_log_cctor(sc_self_type, this, x.get()); + intrusive_ptr_add_ref(ptr_); + } /*cctor*/ + + /* move ctor -- in this case don't need to update refcount */ + intrusive_ptr(intrusive_ptr && x) : ptr_{std::move(x.ptr_)} { + intrusive_ptr_log_mctor(sc_self_type, this, ptr_); + /* since we're moving from x, need to make sure x dtor + * doesn't decrement refcount + */ + x.ptr_ = nullptr; + } + + /* aliasing ctor. see ctor (8) here: + * [[https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr]] + * and this dicsussion: + * [[https://stackoverflow.com/questions/49178231/pybind11-multiple-inheritance-with-custom-holder-type-fails-to-cast-to-base-type/73131206#73131206]] + */ + template + intrusive_ptr(intrusive_ptr const & /*x*/, element_type * y) : ptr_{y} { + if (std::is_same()) { + intrusive_ptr_log_actor(sc_self_type, this, y); + intrusive_ptr_add_ref(ptr_); + ; /* trivial aliasing, proceed */ + } else { + using xo::xtag; + throw std::runtime_error(tostr("attempt to use aliasing ctor with", + xtag("Y", reflect::type_name()), + xtag("T", reflect::type_name()))); + } + } /*ctor*/ + + ~intrusive_ptr() { + T * x = this->ptr_; + + intrusive_ptr_log_dtor(sc_self_type, this, x); + + this->ptr_ = nullptr; + + intrusive_ptr_release(x); + } /*dtor*/ + + static bool compare(intrusive_ptr const & x, + intrusive_ptr const & y) { + return ptrdiff_t(x.get() - y.get()); + } + + Borrow borrow() const; + + T * get() const { return ptr_; } + + T * operator->() const { return ptr_; } + + operator bool() const { return ptr_ != nullptr; } + + intrusive_ptr & operator=(intrusive_ptr const & rhs) { + T * x = rhs.get(); + + intrusive_ptr_log_assign(sc_self_type, this, x); + + T * old = this->ptr_; + this->ptr_ = x; + + intrusive_ptr_add_ref(x); + intrusive_ptr_release(old); + + return *this; + } /*operator=*/ + + intrusive_ptr & operator=(intrusive_ptr && rhs) { + intrusive_ptr_log_massign(sc_self_type, this, rhs.get()); + + std::swap(this->ptr_, rhs.ptr_); + + /* dtor on rhs will decrement refcount on old value of this->ptr_ + * don't increment for new value, since refcount just transfers from rhs to *this + */ + + return *this; + } /*operator=*/ + + private: + static constexpr std::string_view sc_self_type = xo::reflect::type_name>(); + + private: + T * ptr_ = nullptr; + }; /*intrusive_ptr*/ + + template + inline bool operator==(intrusive_ptr const & x, intrusive_ptr const & y) { return intrusive_ptr::compare(x, y) == 0; } + + template + using rp = intrusive_ptr; + + class Refcount { + public: + Refcount() : reference_counter_(0) {} + /* WARNING: virtual dtor here is essential, + * since it's what allows us to invoke delete on a Refcount*, + * for an object of some derived class type T. Otherwise clang + * will use different addresses for Refcount-part and T-part of + * such instance, which means pointer given to delete will not be + * the same as pointer returned from new + */ + virtual ~Refcount() = default; + + uint32_t reference_counter() const { return reference_counter_.load(); } + + private: + friend uint32_t intrusive_ptr_refcount(Refcount *); + friend void intrusive_ptr_add_ref(Refcount *); + friend void intrusive_ptr_release(Refcount *); + + private: + std::atomic reference_counter_; + }; /*Refcount*/ + + inline uint32_t + intrusive_ptr_refcount(Refcount * x) { + /* reporting accurately for diagnostics */ + if (x) + return x->reference_counter_.load(); + else + return 0; + } /*intrusive_ptr_refcount*/ + + void intrusive_ptr_set_debug(bool x); + void intrusive_ptr_log_ctor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + /* here actor short for 'aliasing ctor' */ + void intrusive_ptr_log_actor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + void intrusive_ptr_log_cctor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + void intrusive_ptr_log_mctor(std::string_view const & self_type, + void *this_ptr, + Refcount * x); + void intrusive_ptr_log_dtor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + void intrusive_ptr_log_assign(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + void intrusive_ptr_log_massign(std::string_view const & self_type, + void *this_ptr, + Refcount * x); + void intrusive_ptr_add_ref(Refcount * x); + void intrusive_ptr_release(Refcount * x); + + template + inline std::ostream & + operator<<(std::ostream & os, intrusive_ptr const & x) { + if(x.get()) { + os << *(x.get()); + } else { + os << "() << ">"; + } + return os; + } /*operator<<*/ + + /* borrow a reference-counted pointer to pass down the stack + * 1. borrowed pointer intended to replace: + * a. code like + * foo(rp x), + * passing rp by value requires increment/decrement pair, + * which is superfluous given that caller holds reference throughout + * b. code like + * foo(rp const & x) + * passing rp by reference requires double-indirection in called + * code + * 2. borrowed pointer does not check/maintain reference count. + * it should never be stored in a struct; intended strictly + * to be passed down stack + * 3. just the same, want to be able to copy the borrowed pointer, + * to avoid double-indirection + * 4. also can promote borrowed pointer to full reference-counted + * whenever desired + */ + template + class Borrow { + public: + template + Borrow(rp const & x) : ptr_(x.get()) {} + + Borrow(Borrow const & x) = default; + + /* convert from another borrow, if it has compatible pointer type */ + template + Borrow(Borrow const & x) : ptr_(x.get()) {} + + /* dynamic cast from a pointer to an object of some convertible type */ + template + static Borrow from(Borrow x) { + return Borrow(dynamic_cast(x.get())); + } /*from*/ + + /* promote from native pointer */ + static Borrow from_native(T * x) { + return Borrow(x); + } /*from_native*/ + + T * get() const { return ptr_; } + + rp promote() const { return rp(ptr_); } + + T & operator*() const { return *ptr_; } + T * operator->() const { return ptr_; } + + operator bool() const { return ptr_ != nullptr; } + + static int32_t compare(Borrow const & x, Borrow const & y) { + return ptrdiff_t(x.get() - y.get()); + } /*compare*/ + + static int32_t compare(rp const & x, Borrow const & y) { + return ptrdiff_t(x.get() - y.get()); + } /*compare*/ + + private: + Borrow(T * x) : ptr_(x) {} + + private: + T * ptr_ = nullptr; + }; /*Borrow*/ + + template + inline bool operator==(Borrow x, Borrow y) { return Borrow::compare(x, y) == 0; } + + template + inline bool operator==(rp const & x, Borrow y) { return Borrow::compare(x, y) == 0; } + + template + using brw = Borrow; + + template + Borrow + intrusive_ptr::borrow() const { + return Borrow(*this); + } /*borrow*/ + + } /*namespace ref*/ +} /*namespace xo*/ + +/* end Refcounted.hpp */ diff --git a/include/refcnt/Unowned.hpp b/include/refcnt/Unowned.hpp new file mode 100644 index 00000000..3f78f0d2 --- /dev/null +++ b/include/refcnt/Unowned.hpp @@ -0,0 +1,28 @@ +/* @file Unowned.hpp */ + +namespace xo { + namespace ref { + /* use this is a holder type for pointers that pybind11 should treat + * as "not-my-problem". in particular that pybind11 should never delete. + */ + template + class unowned_ptr { + public: + unowned_ptr(T * x) : ptr_{x} {} + unowned_ptr(unowned_ptr const & x) = default; + ~unowned_ptr() = default; + + T * get() const { return ptr_; } + T * operator->() const { return ptr_; } + + operator bool() const { return ptr_ != nullptr; } + + unowned_ptr & operator=(unowned_ptr const & rhs) = default; + + private: + T * ptr_ = nullptr; + }; /*unowned_ptr*/ + } /*namespace ref*/ +} /*namespace xo*/ + +/* end Unowned.hpp */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 00000000..8083cee0 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,14 @@ +set(SELF_LIBRARY_NAME refcnt) +set(SELF_SOURCE_FILES Refcounted.cpp Displayable.cpp) +add_library(${SELF_LIBRARY_NAME} SHARED ${SELF_SOURCE_FILES}) + +set_target_properties(${SELF_LIBRARY_NAME} + PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION 1) + +xo_indentlog_dependency(${SELF_LIBRARY_NAME}) + +xo_include_options(${SELF_LIBRARY_NAME}) +xo_compile_options(${SELF_LIBRARY_NAME}) +xo_install_library(${SELF_LIBRARY_NAME}) diff --git a/src/Displayable.cpp b/src/Displayable.cpp new file mode 100644 index 00000000..b8793ad3 --- /dev/null +++ b/src/Displayable.cpp @@ -0,0 +1,16 @@ +/* @file Displayable.cpp */ + +#include "refcnt/Displayable.hpp" + +namespace xo { + using xo::tostr; + + namespace ref { + std::string + Displayable::display_string() const { + return tostr(*this); + } /*display_string*/ + } /*namespace ref*/ +} /*namespace xo*/ + +/* end Displayable.cpp */ diff --git a/src/Refcounted.cpp b/src/Refcounted.cpp new file mode 100644 index 00000000..9aeb579d --- /dev/null +++ b/src/Refcounted.cpp @@ -0,0 +1,157 @@ +/* @file Refcounted.cpp */ + +#include "Refcounted.hpp" + +namespace xo { + namespace ref { + namespace { + /* verbose logging for intrusive_ptr */ + static bool s_logging_enabled = false; + + void + intrusive_ptr_log_aux(std::string_view const & self_type, + std::string_view const & method_name, + void * this_ptr, + Refcount * x) + { + scope lscope(XO_LITERAL(verbose, self_type, method_name), + "enter", + xtag("this", this_ptr), + xtag("x", x), + xtag("n", intrusive_ptr_refcount(x))); + } /*intrusive_ptr_log_aux*/ + } /*namespace*/ + + bool + intrusive_ptr_set_debug(bool debug_flag) { + s_logging_enabled = debug_flag; + } /*intrusive_ptr_set_debug*/ + + void + intrusive_ptr_log_ctor(std::string_view const & self_type, + void * this_ptr, + Refcount * x) + { + if (s_logging_enabled) + intrusive_ptr_log_aux(self_type, "::ctor", this_ptr, x); + } /*intrusive_ptr_log_ctor*/ + + void + intrusive_ptr_log_actor(std::string_view const & self_type, + void * this_ptr, + Refcount * x) + { + if (s_logging_enabled) + intrusive_ptr_log_aux(self_type, "::actor", this_ptr, x); + } /*intrusive_ptr_log_actor*/ + + void + intrusive_ptr_log_cctor(std::string_view const & self_type, + void * this_ptr, + Refcount * x) + { + if (s_logging_enabled) + intrusive_ptr_log_aux(self_type, "::cctor", this_ptr, x); + } /*intrusive_ptr_log_cctor*/ + + void + intrusive_ptr_log_mctor(std::string_view const & self_type, + void * this_ptr, + Refcount * x) + { + if (s_logging_enabled) + intrusive_ptr_log_aux(self_type, "::mctor", this_ptr, x); + } /*intrusive_ptr_log_mctor*/ + + void + intrusive_ptr_log_dtor(std::string_view const & self_type, + void * this_ptr, + Refcount * x) + { + if (s_logging_enabled) + intrusive_ptr_log_aux(self_type, "::dtor", this_ptr, x); + } /*intrusive_ptr_log_dtor*/ + + void + intrusive_ptr_log_assign(std::string_view const & self_type, + void * this_ptr, + Refcount * x) + { + if (s_logging_enabled) + intrusive_ptr_log_aux(self_type, "::=", this_ptr, x); + } /*intrusive_ptr_log_assign*/ + + void + intrusive_ptr_log_massign(std::string_view const & self_type, + void * this_ptr, + Refcount * x) + { + if (s_logging_enabled) + intrusive_ptr_log_aux(self_type, "::m=", this_ptr, x); + } /*intrusive_ptr_log_massign*/ + + void + intrusive_ptr_add_ref(Refcount * x) + { + /* for adding reference -- can use relaxed order, + * since any reordering of a set of intrusive_ptr_add_ref() + * calls is ok, provided no intervening intrusive_ptr_release() + * calls. + */ + bool success = (x == nullptr); + + while(!success) { + uint32_t n = x->reference_counter_.load(std::memory_order_relaxed); + + success = x->reference_counter_.compare_exchange_strong(n, n+1, + std::memory_order_relaxed); + } + } /*intrusive_ptr_add_ref*/ + + void + intrusive_ptr_release(Refcount * x) + { + using xo::scope; + using xo::xtag; + + scope log(XO_ENTER0(verbose), + "enter", + xtag("x", x), + xtag("n", x ? x->reference_counter_.load() : 0)); + + /* for decrement, need acq_rel ordering */ + bool success = (x == nullptr); + uint32_t n = 0; + + while(!success) { + n = x->reference_counter_.load(std::memory_order_acquire); + + if(n == static_cast(-1)) { + log && log("detected double-delete attempt", + xtag("x", x), + xtag("n", n)); + assert(false); + } + + success = x->reference_counter_.compare_exchange_strong(n, n-1, + std::memory_order_acq_rel); + } + + if(n == 1) { + /* just deleted the last reference, so recover the object */ + + log && log("delete object with 0 refs"); + + /* for good measure: replace refcount with -1, + * in hope of detecting a double-delete attempt + */ + x->reference_counter_.store(static_cast(-1)); + + delete x; + } + } /*intrusive_ptr_release*/ + + } /*namespace ref*/ +} /*namespace xo*/ + +/* end Refcounted.cpp */ From b6723b921bbd925c4c267ea46705c42281f92018 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 12:27:19 -0400 Subject: [PATCH 0006/2524] refcnt: build + install fixes --- CMakeLists.txt | 40 ++++++++++++++++++++++++++++-- cmake/cxx.cmake | 18 +++++++++++--- cmake/refcntConfig.cmake.in | 4 +++ include/refcnt/Refcounted.hpp | 46 +++++++++++++++++------------------ src/Refcounted.cpp | 2 +- 5 files changed, 80 insertions(+), 30 deletions(-) create mode 100644 cmake/refcntConfig.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e8053f9..ed3c4486 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10) -project(xo-refcnt VERSION 0.1) +project(refcnt VERSION 0.1) enable_language(CXX) include(cmake/cxx.cmake) @@ -13,7 +13,6 @@ include(cmake/code-coverage.cmake) 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. @@ -26,6 +25,7 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) # ---------------------------------------------------------------- # c++ settings +set(XO_PROJECT_NAME refcnt) set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) @@ -43,6 +43,42 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") # sources add_subdirectory(src) +add_subdirectory(utest) + +# ---------------------------------------------------------------- +# cmake export + +set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") +set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") + +include(CMakePackageConfigHelpers) +write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + VERSION 0.1 + COMPATIBILITY AnyNewerVersion +) + +#install( +# TARGETS ${XO_PROJECT_NAME} +# EXPORT ${XO_PROJECT_NAME}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 +# ) + +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} + ) + +install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) +install( + FILES + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" + DESTINATION lib/cmake/${XO_PROJECT_NAME}) # ---------------------------------------------------------------- # install .hpp diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake index 9fc84e60..4f2dd2fa 100644 --- a/cmake/cxx.cmake +++ b/cmake/cxx.cmake @@ -14,9 +14,11 @@ macro(xo_include_options target) # target_include_directories( ${target} PUBLIC - ${PROJECT_SOURCE_DIR}/include # e.g. for #include "indentlog/scope.hpp" - ${PROJECT_SOURCE_DIR}/include/${target} # e.g. for #include "Refcounted.hpp" in refcnt/src - ${PROJECT_BINARY_DIR} # e.g. for generated config.hpp file + $ # e.g. for #include "indentlog/scope.hpp" + $ + $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ + $ # e.g. for generated config.hpp file ) # ---------------------------------------------------------------- @@ -72,7 +74,15 @@ endmacro() # use this for a subdir that builds a library # macro(xo_install_library target) - install(TARGETS ${target} DESTINATION lib) + 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() # ---------------------------------------------------------------- diff --git a/cmake/refcntConfig.cmake.in b/cmake/refcntConfig.cmake.in new file mode 100644 index 00000000..e13a2c54 --- /dev/null +++ b/cmake/refcntConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/refcnt/Refcounted.hpp b/include/refcnt/Refcounted.hpp index 17d778c7..dc069cf5 100644 --- a/include/refcnt/Refcounted.hpp +++ b/include/refcnt/Refcounted.hpp @@ -171,31 +171,31 @@ namespace xo { return 0; } /*intrusive_ptr_refcount*/ - void intrusive_ptr_set_debug(bool x); - void intrusive_ptr_log_ctor(std::string_view const & self_type, - void * this_ptr, - Refcount * x); + extern void intrusive_ptr_set_debug(bool x); + extern void intrusive_ptr_log_ctor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); /* here actor short for 'aliasing ctor' */ - void intrusive_ptr_log_actor(std::string_view const & self_type, - void * this_ptr, - Refcount * x); - void intrusive_ptr_log_cctor(std::string_view const & self_type, - void * this_ptr, - Refcount * x); - void intrusive_ptr_log_mctor(std::string_view const & self_type, - void *this_ptr, - Refcount * x); - void intrusive_ptr_log_dtor(std::string_view const & self_type, - void * this_ptr, - Refcount * x); - void intrusive_ptr_log_assign(std::string_view const & self_type, - void * this_ptr, - Refcount * x); - void intrusive_ptr_log_massign(std::string_view const & self_type, - void *this_ptr, + extern void intrusive_ptr_log_actor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + extern void intrusive_ptr_log_cctor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + extern void intrusive_ptr_log_mctor(std::string_view const & self_type, + void *this_ptr, + Refcount * x); + extern void intrusive_ptr_log_dtor(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + extern void intrusive_ptr_log_assign(std::string_view const & self_type, + void * this_ptr, + Refcount * x); + extern void intrusive_ptr_log_massign(std::string_view const & self_type, + void *this_ptr, Refcount * x); - void intrusive_ptr_add_ref(Refcount * x); - void intrusive_ptr_release(Refcount * x); + extern void intrusive_ptr_add_ref(Refcount * x); + extern void intrusive_ptr_release(Refcount * x); template inline std::ostream & diff --git a/src/Refcounted.cpp b/src/Refcounted.cpp index 9aeb579d..11c8cb62 100644 --- a/src/Refcounted.cpp +++ b/src/Refcounted.cpp @@ -22,7 +22,7 @@ namespace xo { } /*intrusive_ptr_log_aux*/ } /*namespace*/ - bool + void intrusive_ptr_set_debug(bool debug_flag) { s_logging_enabled = debug_flag; } /*intrusive_ptr_set_debug*/ From f8ca4dbe096b50c9628ba7e64ce46fde5d304d04 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 12:56:22 -0400 Subject: [PATCH 0007/2524] refcnt: + unit test --- utest/CMakeLists.txt | 60 +++++++ utest/README | 7 + utest/intrusive_ptr.test.cpp | 301 +++++++++++++++++++++++++++++++++++ utest/refcnt_utest_main.cpp | 6 + 4 files changed, 374 insertions(+) create mode 100644 utest/CMakeLists.txt create mode 100644 utest/README create mode 100644 utest/intrusive_ptr.test.cpp create mode 100644 utest/refcnt_utest_main.cpp diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..74706592 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,60 @@ +# build unittest 'refcnt/utest/utest.refcnt + +set(SELF_EXECUTABLE_NAME utest.refcnt) + +# These tests can use the Catch2-provided main +set(SELF_SOURCE_FILES intrusive_ptr.test.cpp refcnt_utest_main.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) + +#target_link_libraries(${SELF_EXECUTABLE_NAME} PRIVATE Catch2::Catch2WithMain) + +# ---------------------------------------------------------------- +# 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: refcnt, ... + +target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC refcnt) + +# ---------------------------------------------------------------- +# 3rd part dependency: catch2: + +find_package(Catch2 2 REQUIRED) + +# need this so that catch2/include appears in compile_commands.json, +# on which lsp integration relies. +# +# See also /nix/store/*-catch2-*/lib/cmake/Catch2/ParseAndAddCatchTests.cmake; +# commands here derived from ^ .cmake file +# +#find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") +#target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC ${CATCH_INCLUDE_DIR}) + +# ---------------------------------------------------------------- +# make standard directories for std:: includes explicit +# so that +# (1) they appear in compile_commands.json. +# (2) clangd (run from emacs lsp-mode) can find them +# +if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +endif() + +# end CMakeLists.txt diff --git a/utest/README b/utest/README new file mode 100644 index 00000000..85cc27c2 --- /dev/null +++ b/utest/README @@ -0,0 +1,7 @@ +* to run unit tests for this directoyr + + $ cd path/to/kalman/build + $ ./refcnt/utest/utest.refcnt + + + \ No newline at end of file diff --git a/utest/intrusive_ptr.test.cpp b/utest/intrusive_ptr.test.cpp new file mode 100644 index 00000000..d8d756e2 --- /dev/null +++ b/utest/intrusive_ptr.test.cpp @@ -0,0 +1,301 @@ +/* @file intrusive_ptr.test.cpp */ + +#include "refcnt/Refcounted.hpp" +#include "indentlog/scope.hpp" +#include "catch2/catch.hpp" +#include +#include + +namespace xo { + using xo::ref::Refcount; + using xo::ref::Borrow; + using xo::ref::rp; + using xo::ref::brw; + using xo::ref::intrusive_ptr_refcount; + using xo::ref::intrusive_ptr_add_ref; + using xo::ref::intrusive_ptr_release; + + namespace ut { + namespace { + static uint32_t ctor_count = 0; + static uint32_t dtor_count = 0; + + /* empty object, except for refcount */ + class JustRefcount : public ref::Refcount { + public: + JustRefcount() { ++ctor_count; } + ~JustRefcount() { ++dtor_count; } + }; /*JustRefcount*/ + + inline std::ostream & operator<<(std::ostream & os, JustRefcount & x) { + os << "JustRefcount"; + return os; + } /*operator<<*/ + } /*namespace*/ + + TEST_CASE("refcount", "[refcnt][trivial]") { + REQUIRE(std::is_default_constructible() == true); + REQUIRE(std::has_virtual_destructor() == true); + + /* refcount object self-initializes to 0 */ + Refcount x; + REQUIRE(x.reference_counter() == 0); + } /*TEST_CASE(refcount)*/ + + TEST_CASE("null-intrusive-ptr", "[refcnt][trivial]") { + //constexpr std::string_view c_self = "TEST_CASE:null-intrusive-ptr"; + + REQUIRE(std::has_virtual_destructor() == true); + + rp p1; + rp p2; + + REQUIRE(sizeof(p1) == sizeof(JustRefcount*)); + + REQUIRE(p1.get() == nullptr); + REQUIRE(p1.operator->() == nullptr); + + REQUIRE(p2.get() == nullptr); + REQUIRE(p2.operator->() == nullptr); + + /* can assign a nullptr */ + rp p3; + + REQUIRE(p3.get() == nullptr); + p3 = p1; + REQUIRE(p3.get() == nullptr); + + /* can use aux functions on null pointers */ + REQUIRE(intrusive_ptr_refcount(p1.get()) == 0); + + intrusive_ptr_add_ref(nullptr); + intrusive_ptr_release(nullptr); + + /* can borrow a null intrusive_ptr */ + brw p1_brw = p1.borrow(); + brw p2_brw = p2.borrow(); + + REQUIRE(p1_brw.get() == nullptr); + REQUIRE(p1_brw.operator->() == nullptr); + /* null borrow is false-y */ + REQUIRE(p1_brw == false); + + /* can promote a borrowed pointer */ + rp pp = p1_brw.promote(); + + REQUIRE(p1.get() == pp.get()); + + /* comparisons */ + REQUIRE(Borrow::compare(p1_brw, p2_brw) == 0); + REQUIRE(p1_brw == p2_brw); + REQUIRE((p1_brw != p2_brw) == false); + REQUIRE(p1 == p1_brw); + REQUIRE((p1 != p1_brw) == false); + REQUIRE(p1_brw == p1); + REQUIRE((p1_brw != p1) == false); + } /*TEST_CASE(null-intrusive_ptr)*/ + + TEST_CASE("intrusive-ptr-identity", "[refcnt][identity]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; + + rp p1(new JustRefcount()); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1.get() == p1.operator->()); + REQUIRE(intrusive_ptr_refcount(p1.get()) == 1); + REQUIRE(p1->reference_counter() == 1); + + intrusive_ptr_add_ref(p1.get()); + + REQUIRE(intrusive_ptr_refcount(p1.get()) == 2); + + intrusive_ptr_release(p1.get()); + + REQUIRE(intrusive_ptr_refcount(p1.get()) == 1); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + rp p2(new JustRefcount()); + + REQUIRE(ctor_count == cc + 2); + REQUIRE(dtor_count == dc); + + REQUIRE(p2.get() != nullptr); + REQUIRE(p2.get() != p1.get()); + REQUIRE(p2.get() == p2.operator->()); + REQUIRE(p2->reference_counter() == 1); + + /* can borrow a non-null intrusive-ptr */ + brw p1_brw = p1.borrow(); + + REQUIRE(p1_brw.get() == p1.get()); + + /* borrowing does not change refcount, borrow not tracked */ + REQUIRE(ctor_count == cc + 2); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get()->reference_counter() == 1); + + /* copying borrowed pointer does not touch refcount */ + brw p1_brw2 = p1_brw; + + REQUIRE(ctor_count == cc + 2); + REQUIRE(dtor_count == dc); + REQUIRE(p1_brw2.get() == p1.get()); + + REQUIRE(p1.get()->reference_counter() == 1); + } /*TEST_CASE(identity-intrusive-ptr)*/ + + TEST_CASE("intrusive-ptr-release", "[refcnt][release]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; + + rp p1(new JustRefcount()); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); + + /* reference count going to 0 -> delete object */ + p1 = nullptr; + + REQUIRE(p1.get() == nullptr); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-release)*/ + + TEST_CASE("intrusive-ptr-copy", "[refcnt][copy]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; + + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); + + /* copy ctor ran to make copy of p1, did not allocate */ + rp p2(p1); + + REQUIRE(p1->reference_counter() == 2); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + } /*TEST_CASE(intrusive-ptr-copy)*/ + + TEST_CASE("intrusive-ptr-move", "[refcnt][move]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; + + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); + + rp p2{std::move(p1)}; + + REQUIRE(p2->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + p2 = nullptr; + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-move)*/ + + TEST_CASE("instrusive-ptr-assign", "[refcnt][assign]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; + + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); + + rp p2; + + REQUIRE(p2.get() == nullptr); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + p2 = p1; + + REQUIRE(p2.get() == p1.get()); + REQUIRE(p2->reference_counter() == 2); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + p1 = nullptr; + + REQUIRE(p2->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + p2 = nullptr; + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-assign)*/ + + TEST_CASE("intrusive-ptr-move-assign", "[refcnt][move-assign]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; + + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); + + rp p2; + + REQUIRE(p2.get() == nullptr); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + p2 = std::move(p1); + + REQUIRE(p1.get() == nullptr); + REQUIRE(p2.get() == p1_native); + REQUIRE(p2->reference_counter() == 1); + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + p1 = nullptr; /*no-op*/ + + REQUIRE(p2->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + + p2 = nullptr; + + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-move-assign)*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end intrusive_ptr.test.cpp */ diff --git a/utest/refcnt_utest_main.cpp b/utest/refcnt_utest_main.cpp new file mode 100644 index 00000000..327713b7 --- /dev/null +++ b/utest/refcnt_utest_main.cpp @@ -0,0 +1,6 @@ +/* @file refcnt_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end refcnt_utest_main.cpp */ From 1bdb8bb459f6aecfd6c7eccd731ac3aeda294021 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:06:25 -0400 Subject: [PATCH 0008/2524] github actions attempt --- .github/cmake-single-platform.yml | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/cmake-single-platform.yml diff --git a/.github/cmake-single-platform.yml b/.github/cmake-single-platform.yml new file mode 100644 index 00000000..542d220d --- /dev/null +++ b/.github/cmake-single-platform.yml @@ -0,0 +1,42 @@ +# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml +name: CMake on a single platform + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + +# - name: Configure CMake +# # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. +# # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type +# run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + +# - name: Build +# # Build your program with the given configuration +# run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + +# - name: Test +# working-directory: ${{github.workspace}}/build +# # Execute tests defined by the CMake configuration. +# # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail +# run: ctest -C ${{env.BUILD_TYPE}} From 9161210d5237659680d7d5f8fd86debcd366329d Mon Sep 17 00:00:00 2001 From: Roland Date: Sun, 24 Sep 2023 13:09:31 -0400 Subject: [PATCH 0009/2524] Create main.yml --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1 @@ + From 572572c7bb1fb3f7c5b689519ae175fd456aeae1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:10:35 -0400 Subject: [PATCH 0010/2524] github actions, take 2 --- .github/{ => workflows}/cmake-single-platform.yml | 0 .github/workflows/main.yml | 1 - 2 files changed, 1 deletion(-) rename .github/{ => workflows}/cmake-single-platform.yml (100%) delete mode 100644 .github/workflows/main.yml diff --git a/.github/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml similarity index 100% rename from .github/cmake-single-platform.yml rename to .github/workflows/cmake-single-platform.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 8b137891..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1 +0,0 @@ - From 8340c0c6159f8cfda4779d7a33837789c3c08f99 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:13:55 -0400 Subject: [PATCH 0011/2524] github actions, take 3 --- .github/workflows/cmake-single-platform.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 542d220d..749f36c1 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -26,6 +26,10 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + - name: Fetch indentlog + # fetch source tree for indentlog dependency + run: git clone git@github.com:rconybea/indentlog.git + # - name: Configure CMake # # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 464449df065ea3857ad25b122f38c14c7c3c0036 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:23:09 -0400 Subject: [PATCH 0012/2524] github actions, take 4 --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 749f36c1..fb4ec30d 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -28,7 +28,7 @@ jobs: - name: Fetch indentlog # fetch source tree for indentlog dependency - run: git clone git@github.com:rconybea/indentlog.git + run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git # - name: Configure CMake # # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. From 8f596d8f3d973287eaab31b390d4e8a77aa50ab5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:28:31 -0400 Subject: [PATCH 0013/2524] github actions, take 5 --- .github/workflows/cmake-single-platform.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index fb4ec30d..505cdc1c 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -26,7 +26,11 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 - - name: Fetch indentlog + - name: Clone indentlog + with: + repository: Rconybea/indentlog + path: repo/indentlog + # fetch source tree for indentlog dependency run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git From 264da8e122ff49104aa6aa7deda3fac062e7bd4f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:29:34 -0400 Subject: [PATCH 0014/2524] bugfix: typo in .tm --- .github/workflows/cmake-single-platform.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 505cdc1c..92e5ac73 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -31,8 +31,8 @@ jobs: repository: Rconybea/indentlog path: repo/indentlog - # fetch source tree for indentlog dependency - run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git +# # fetch source tree for indentlog dependency +# run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git # - name: Configure CMake # # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. From a605654fb9aa64dfbf57b915fb4136fe0ff9cb25 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:30:41 -0400 Subject: [PATCH 0015/2524] bugfix: missing 'uses: ..' in .yml --- .github/workflows/cmake-single-platform.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 92e5ac73..cd9aca1a 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -27,6 +27,7 @@ jobs: run: sudo apt-get install -y catch2 - name: Clone indentlog + uses: actions/checkout@v3 with: repository: Rconybea/indentlog path: repo/indentlog From 2fc39afdcc8979e7288ee450a317d04467117b05 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:36:16 -0400 Subject: [PATCH 0016/2524] github actions, take 6 --- .github/workflows/cmake-single-platform.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index cd9aca1a..26c36193 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -32,6 +32,10 @@ jobs: repository: Rconybea/indentlog path: repo/indentlog + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + # # fetch source tree for indentlog dependency # run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git From be3880779f1a078e879b68e8b4bbce143445642c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:37:59 -0400 Subject: [PATCH 0017/2524] github: + indentlog build action --- .github/workflows/cmake-single-platform.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 26c36193..d943af45 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -36,6 +36,9 @@ jobs: # configure cmake for indentlog in dedicated build directory. run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + # # fetch source tree for indentlog dependency # run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git From 7cc51fc13de69c09e07358aef3072e36d421aec1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:40:07 -0400 Subject: [PATCH 0018/2524] github: install indentlog --- .github/workflows/cmake-single-platform.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index d943af45..9fceb907 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -39,6 +39,10 @@ jobs: - name: Build indentlog run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --build ${{github.workspace}}/build_indentlog install + # # fetch source tree for indentlog dependency # run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git From 9a3b104ec01084424e35b30c9b2b6c2ed02e01d9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:42:04 -0400 Subject: [PATCH 0019/2524] bugfix: cmake --install syntax --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 9fceb907..4f435149 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -41,7 +41,7 @@ jobs: - name: Install indentlog # install into ${{github.workspace}}/local - run: cmake --build ${{github.workspace}}/build_indentlog install + run: cmake --install ${{github.workspace}}/build_indentlog # # fetch source tree for indentlog dependency # run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git From f305581cdf43a456257e794f909bc39aa2de477a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:47:08 -0400 Subject: [PATCH 0020/2524] github: try building refcnt, now with indentlog dep --- .github/workflows/cmake-single-platform.yml | 27 +++++++++------------ 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 4f435149..ed2bd910 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -43,20 +43,17 @@ jobs: # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_indentlog -# # fetch source tree for indentlog dependency -# run: git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com:rconybea/indentlog.git + - name: Configure refcnt + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -# - name: Configure CMake -# # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. -# # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type -# run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + - name: Build refcnt + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} -# - name: Build -# # Build your program with the given configuration -# run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - -# - name: Test -# working-directory: ${{github.workspace}}/build -# # Execute tests defined by the CMake configuration. -# # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail -# run: ctest -C ${{env.BUILD_TYPE}} + - name: Test refcnt + working-directory: ${{github.workspace}}/build_recnt + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From df4420c6a5e7b8f6cd18fc4174e14c27a1cd4fb5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:49:09 -0400 Subject: [PATCH 0021/2524] github: bugfix: typo in .yml --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index ed2bd910..5d74447d 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -53,7 +53,7 @@ jobs: run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} - name: Test refcnt - working-directory: ${{github.workspace}}/build_recnt + working-directory: ${{github.workspace}}/build_refcnt # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} From 34e0a8acea5ea2cfa35a35f6630d68ce1c4c4ace Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:53:38 -0400 Subject: [PATCH 0022/2524] github: streamline attempt in .yml --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 5d74447d..29cb9534 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -34,7 +34,7 @@ jobs: - name: Configure indentlog # configure cmake for indentlog in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{env.LOCAL_INSTALL_PREFIX}} repo/indentlog - name: Build indentlog run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} From 4b3c854fc7872ba404ce4e0af25ecfdc47822ae1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 13:56:34 -0400 Subject: [PATCH 0023/2524] github: abandon streamline experiment, not sucessful --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 29cb9534..5d74447d 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -34,7 +34,7 @@ jobs: - name: Configure indentlog # configure cmake for indentlog in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{env.LOCAL_INSTALL_PREFIX}} repo/indentlog + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog - name: Build indentlog run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} From 8560fc63fdda5fa208e42e3389bec454eaa8870f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 14:42:38 -0400 Subject: [PATCH 0024/2524] initial commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..b43d50c1 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# subsys repo From 1b363f94a08f7a0a8f24085a131fe63ec728dbf1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 15:33:23 -0400 Subject: [PATCH 0025/2524] subsys: build+install (header-only library) --- CMakeLists.txt | 93 +++++ cmake/code-coverage.cmake | 678 +++++++++++++++++++++++++++++++++++ cmake/cxx.cmake | 96 +++++ cmake/subsysConfig.cmake.in | 4 + include/subsys/Subsystem.hpp | 303 ++++++++++++++++ 5 files changed, 1174 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/code-coverage.cmake create mode 100644 cmake/cxx.cmake create mode 100644 cmake/subsysConfig.cmake.in create mode 100644 include/subsys/Subsystem.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..c63d6740 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,93 @@ +cmake_minimum_required(VERSION 3.10) + +project(subsys VERSION 0.1) +enable_language(CXX) + +include(cmake/cxx.cmake) +include(cmake/code-coverage.cmake) + +enable_testing() +# activate code coverage for all executables + libraries (when -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. +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) + +set(XO_PROJECT_NAME subsys) + +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 "") + +# ---------------------------------------------------------------- +# - author's convenience: default install prefix to /home/$USER/local +# - otherwise use -DCMAKE_INSTALL_PREFIX=/path/to/somewhere + +if(NOT USER) + set(USER $ENV{USER}) +endif() + +# hmm. this works if explicitly given with cmake: +# cmake -DCMAKE_INSTALL_PREFIX=/home/roland/local path/to/source +# but not as default +if(NOT CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") +endif() +if(NOT CMAKE_INSTALL_RPATH) + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath in installed libraries/executables") +endif() + +#add_subdirectory(example) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# installing header-only library + +add_library(subsys INTERFACE) +target_include_directories(subsys INTERFACE + $ + $ +) +xo_install_library(subsys) + +# ---------------------------------------------------------------- +# cmake export +# (so this library works with cmake's find_package()) + +set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") +set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") + +include(CMakePackageConfigHelpers) +write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + VERSION 0.1 + COMPATIBILITY AnyNewerVersion +) + +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} + ) + +install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) +install( + FILES + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" + DESTINATION lib/cmake/${XO_PROJECT_NAME}) + +# ---------------------------------------------------------------- +# install .hpp + +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) + +# end CMakeLists.txt diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake new file mode 100644 index 00000000..b6b36064 --- /dev/null +++ b/cmake/code-coverage.cmake @@ -0,0 +1,678 @@ +# +# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# USAGE: To enable any code coverage instrumentation/targets, the single CMake +# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or +# on the command line. +# +# From this point, there are two primary methods for adding instrumentation to +# targets: +# +# 1 - A blanket instrumentation by calling `add_code_coverage()`, where +# all targets in that directory and all subdirectories are automatically +# instrumented. +# +# 2 - Per-target instrumentation by calling +# `target_code_coverage()`, where the target is given and thus only +# that target is instrumented. This applies to both libraries and executables. +# +# To add coverage targets, such as calling `make ccov` to generate the actual +# coverage information for perusal or consumption, call +# `target_code_coverage()` on an *executable* target. +# +# Example 1: All targets instrumented +# +# In this case, the coverage information reported will will be that of the +# `theLib` library target and `theExe` executable. +# +# 1a: Via global command +# +# ~~~ +# add_code_coverage() # Adds instrumentation to all targets +# +# add_library(theLib lib.cpp) +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target +# # (instrumentation already added via global anyways) +# # for generating code coverage reports. +# ~~~ +# +# 1b: Via target commands +# +# ~~~ +# add_library(theLib lib.cpp) +# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. +# ~~~ +# +# Example 2: Target instrumented, but with regex pattern of files to be excluded +# from report +# +# ~~~ +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ +# +# Example 3: Target added to the 'ccov' and 'ccov-all' targets +# +# ~~~ +# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. +# +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ + +# Options +option( + CODE_COVERAGE + "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" + OFF) + +# Programs +find_program(LLVM_COV_PATH llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) +# Hide behind the 'advanced' mode flag for GUI/ccmake +mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) + +# Variables +set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) +set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) + +# Common initialization/checks +if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) + set(CODE_COVERAGE_ADDED ON) + + # Common Targets + add_custom_target( + ccov-preprocessing + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} + DEPENDS ccov-clean) + + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + # Messages + message(STATUS "Building with llvm Code Coverage Tools") + + if(NOT LLVM_COV_PATH) + message(FATAL_ERROR "llvm-cov not found! Aborting.") + else() + # Version number checking for 'EXCLUDE' compatibility + execute_process(COMMAND ${LLVM_COV_PATH} --version + OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION + ${LLVM_COV_VERSION_CALL_OUTPUT}) + + if(LLVM_COV_VERSION VERSION_LESS "7.0.0") + message( + WARNING + "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" + ) + endif() + endif() + + # Targets + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + else() + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + endif() + + # Used to get the shared object file list before doing the main all- + # processing + add_custom_target( + ccov-libs + COMMAND ; + COMMENT "libs ready for coverage report.") + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + # Messages + message(STATUS "Building with lcov Code Coverage Tools") + + if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) + if(NOT ${upper_build_type} STREQUAL "DEBUG") + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + else() + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Targets + add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory + ${CMAKE_BINARY_DIR} --zerocounters) + + else() + message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") + endif() +endif() + +# Adds code coverage instrumentation to a library, or instrumentation/targets +# for an executable target. +# ~~~ +# EXECUTABLE ADDED TARGETS: +# GCOV/LCOV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# +# LLVM-COV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report. +# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. +# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. +# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. +# ccov-all-export : Exports the coverage report to a JSON file. +# +# Required: +# TARGET_NAME - Name of the target to generate code coverage for. +# Optional: +# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. +# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. +# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) +# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. +# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. +# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory +# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** +# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call +# ~~~ +function(target_code_coverage TARGET_NAME) + # Argument parsing + set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) + set(single_value_keywords COVERAGE_TARGET_NAME) + set(multi_value_keywords EXCLUDE OBJECTS ARGS) + cmake_parse_arguments( + target_code_coverage "${options}" "${single_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + # Set the visibility of target functions to PUBLIC, INTERFACE or default to + # PRIVATE. + if(target_code_coverage_PUBLIC) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY PUBLIC) + elseif(target_code_coverage_INTERFACE) + set(TARGET_VISIBILITY INTERFACE) + set(TARGET_LINK_VISIBILITY INTERFACE) + elseif(target_code_coverage_PLAIN) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY) + else() + set(TARGET_VISIBILITY PRIVATE) + set(TARGET_LINK_VISIBILITY PRIVATE) + endif() + + if(NOT target_code_coverage_COVERAGE_TARGET_NAME) + # If a specific name was given, use that instead. + set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) + endif() + + if(CODE_COVERAGE) + + # Add code coverage instrumentation to the target's linker command + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs + -ftest-coverage) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) + endif() + + # Targets + get_target_property(target_type ${TARGET_NAME} TYPE) + + # Add shared library to processing for 'all' targets + if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" >> + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + if(NOT TARGET ccov-libs) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-libs + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # If there are shared objects to also work with, generate the string to + # add them here + foreach(SO_TARGET ${target_code_coverage_OBJECTS}) + # Check to see if the target is a shared object + if(TARGET ${SO_TARGET}) + get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) + if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") + set(SO_OBJECTS ${SO_OBJECTS} -object=$) + endif() + endif() + endforeach() + + # Run the executable, generating raw profile data Make the run data + # available for further processing. Separated to allow Windows to run + # this target serially. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list + JOB_POOL ccov_serial_pool + DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) + + # Merge the generated profile data so llvm-cov can process it + add_custom_target( + ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_PROFDATA_PATH} merge -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Ignore regex only works on LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print out details of the coverage information to the command line + add_custom_target( + ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Print out a summary of the coverage information to the command line + add_custom_target( + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) + + # Run the executable, generating coverage information + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND $ ${target_code_coverage_ARGS} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + # Generate exclusion string for use + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + if(NOT ${target_code_coverage_EXTERNAL}) + set(EXTERNAL_OPTION --no-external) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + else() + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + endif() + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + + add_custom_command( + TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." + ) + + # AUTO + if(target_code_coverage_AUTO) + if(NOT TARGET ccov) + add_custom_target(ccov) + endif() + add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) + + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) + endif() + add_dependencies( + ccov-report + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # ALL + if(target_code_coverage_ALL) + if(NOT TARGET ccov-all-processing) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-all-processing + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + endif() +endfunction() + +# Adds code coverage instrumentation to all targets in the current directory and +# any subdirectories. To add coverage instrumentation to only specific targets, +# use `target_code_coverage`. +function(add_code_coverage) + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_compile_options(-fprofile-instr-generate -fcoverage-mapping) + add_link_options(-fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options(-fprofile-arcs -ftest-coverage) + link_libraries(gcov) + endif() + endif() +endfunction() + +# Adds the 'ccov-all' type targets that calls all targets added via +# `target_code_coverage` with the `ALL` parameter, but merges all the coverage +# data from them into a single large report instead of the numerous smaller +# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for +# use with coverage dashboards (e.g. codecov.io, coveralls). +# ~~~ +# Optional: +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! +# ~~~ +function(add_code_coverage_all_targets) + # Argument parsing + set(multi_value_keywords EXCLUDE) + cmake_parse_arguments(add_code_coverage_all_targets "" "" + "${multi_value_keywords}" ${ARGN}) + + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # Merge the profile data for all of the run executables + if(WIN32) + add_custom_target( + ccov-all-processing + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe + merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -sparse $$FILELIST) + else() + add_custom_target( + ccov-all-processing + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + endif() + + # Regex exclude only available for LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print summary of the code coverage information to the command line + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-all-export + COMMAND + ${LLVM_COV_PATH} export `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + + # Generate HTML output of all added targets for perusal + if(WIN32) + add_custom_target( + ccov-all + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") + + # Nothing required for gcov + add_custom_target(ccov-all-processing COMMAND ;) + + # Exclusion regex string creation + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + else() + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + endif() + + # Generates HTML output of all targets for perusal + add_custom_target( + ccov-all + COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} + DEPENDS ccov-all-capture) + + endif() + + add_custom_command( + TARGET ccov-all + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." + ) + endif() +endfunction() diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake new file mode 100644 index 00000000..7fa32372 --- /dev/null +++ b/cmake/cxx.cmake @@ -0,0 +1,96 @@ +# ---------------------------------------------------------------- +# use this in subdirs that compile c++ code +# +macro(xo_include_options target) + # ---------------------------------------------------------------- + # PROJECT_SOURCE_DIR: + # so we can for example write + # #include "ordinaltree/foo.hpp" + # from anywhere in the project + # PROJECT_BINARY_DIR: + # since generated version file will be in build directory, + # need that build directory to also appear in + # compiler's include path + # + target_include_directories( + ${target} PUBLIC + $ # e.g. for #include "indentlog/scope.hpp" + $ + $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ + $ # e.g. for generated config.hpp file + ) + + # ---------------------------------------------------------------- + # make standard directories for std:: includes explicit + # so that + # (1) they appear in compile_commands.json. + # (2) clangd (run from emacs lsp-mode) can find them + # + if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) + endif() +endmacro() + +# ---------------------------------------------------------------- +# variable +# XO_ADDRESS_SANITIZE +# determines whether to enable address sanitizer for the XO project +# (see toplevel CMakeLists.txt) +# ---------------------------------------------------------------- +if(XO_ADDRESS_SANITIZE) + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() + +# XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF +set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) + +# XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON +# +# address sanitizer build complains about _FORTIFY_SOURCE redefines +# In file included from :460: +# :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] +# #define _FORTIFY_SOURCE 2 +# +set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) + +# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro +if(XO_ADDRESS_SANITIZE) + set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) +else() + set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) +endif() + +# ---------------------------------------------------------------- +# generally want all the errors+warnings! +# however: address sanitizer generates error on _FORTIFY_SOURCE +# +macro(xo_compile_options target) + target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) +endmacro() + +# ---------------------------------------------------------------- +# use this for a subdir that builds a library +# +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_indentlogg_dependency target) + find_package(indentlog REQUIRED) + #add_dependencies(${target} indentlog) + target_link_libraries(${target} PUBLIC indentlog) + #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) +endmacro() diff --git a/cmake/subsysConfig.cmake.in b/cmake/subsysConfig.cmake.in new file mode 100644 index 00000000..e13a2c54 --- /dev/null +++ b/cmake/subsysConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/subsys/Subsystem.hpp b/include/subsys/Subsystem.hpp new file mode 100644 index 00000000..b9a41e18 --- /dev/null +++ b/include/subsys/Subsystem.hpp @@ -0,0 +1,303 @@ +/* file Subsystem.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "indentlog/scope.hpp" +#include +#include +#include +#include + +/* e.g. XO_SUBSYSTEM_TAG(simulator) => xo::S_simulator_tag */ +#define XO_SUBSYSTEM_TAG(subsys_name) xo::S_ ## subsys_name ## _tag + +/* e.g. XO_SUBSYSTEM_REQUIRE(simulator) => + * xo::InitSubsys::require() + */ +#define XO_SUBSYSTEM_REQUIRE(subsys_name) xo::InitSubsys::require(); + +/* e.g. XO_SUBSYSTEM_PROVIDE(simulator, &init) => + * xo::Subsystem::provide("simulator", &init) + */ +#define XO_SUBSYSTEM_PROVIDE(subsys_name, init_addr) xo::Subsystem::provide(STRINGIFY(subsys_name), init_addr) + +//#define VERIFY_SUBSYSTEM(tag) Subsystem::verify_present(STRINGIFY(tag)) + +namespace xo { + using xo::tostr; + + /* evidence that one or more subsystems have been initialized. + * Used to prevent static linker stripping must-run initialization code + */ + class InitEvidence { + public: + InitEvidence() = default; + InitEvidence(std::uint64_t x) : evidence_{x} {} + + std::uint64_t evidence() const { return evidence_; } + + InitEvidence operator^=(InitEvidence x) { + this->evidence_ ^= x.evidence_; + + return *this; + } /*operator^=*/ + + InitEvidence operator^(InitEvidence x) { + return InitEvidence(this->evidence_ ^ x.evidence_); + } + + private: + /* we don't care about the specific value computed here, + * purpose is to be sufficiently impenentrable to compiler such + * that static linker can't optimize it away + */ + std::uint64_t evidence_ = 0; + }; /*InitEvidence*/ + + /* Goals: + * 1. provide for code that must run once (and only once) + * to initialize subsystems + * 2. in executable, want to run such code after main() starts + * instead of relying on static initializers; + * that way init behavior can be parameterized based on + * program arguments + * + * Use + * // subsystem foo + * + * enum Foo_tag {}; + * + * // guarantees that if anything gets initialized, then + * // foo_init() is included + * // + * template<> + * struct InitSubsys { + * static void foo_init() { ... } + * + * static InitEvidence require() { + * return Subsystem::require("foo", &foo_init); + * } + * }; + * + * .. register other subsystems .. + * + * Subsystem::initialize_all(); // foo_init() has been called once + * + * If subsystem bar depends on supporting subsystem {foo, quux}, then write: + * + * enum Bar_tag {}; + * + * template<> + * struct InitSubsys { + * static void bar_init() { ... } + * + * static InitEvidence require() { + * InitEvidence retval; + * + * retval ^= InitSubsys::require(); + * retval ^= InitSubsys::require(); + * + * retval ^= Subsystem::require("bar", &bar_init); + * } + * }; + * + * If using subsystems from a shared library (so no access to cmdline args etc): + * e.g. in pyfoo.cpp: + * + * InitEvidence s_pyfoo_init = InitSubsys::require(); + * or + * InitEvidence s_pyfoo_init = (InitSubsys::require() + * ^ InitSubsys::require()); + * + * Note: Tag argument here no relation of BuildTag in SubsystemImpl below + */ + template + struct InitSubsys {}; + + /* BuildTag: placeholder; insisting on header-only library */ + template + class SubsystemImpl { + public: + SubsystemImpl() = default; + SubsystemImpl(bool require_flag, + std::string_view subsys_name, + std::function init_fn) + : require_flag_{require_flag}, + subsys_name_{subsys_name}, + init_fn_{init_fn} {} + + /* establish an empty Subsystem record for subsys_name. + * record is _not_ linked into s_subsys_l! + * idempotent. + */ + template + static SubsystemImpl * establish() { + static SubsystemImpl s_subsys; + + return &s_subsys; + } /*establish*/ + + template + static bool verify_present(std::string subsys_tag) { + SubsystemImpl * subsys = establish(); + + if (!subsys->require_flag()) { + throw std::runtime_error(tostr("subsystem not present." + "(missing InitSubsys<", subsys_tag, ">::require() ?)")); + return false; + } + + return true; + } /*verify_present*/ + + /* provide (once only) initialization code for a subsystem with tag SubsystemTag. + * ideally this would be called just once for a particular tag; + * if called multiple times, calls after the first are no-ops. + */ + template + static InitEvidence provide(std::string_view subsys_name, + std::function init_fn) { + SubsystemImpl * subsys = establish(); + + provide_aux(subsys_name, init_fn, subsys); + + return InitEvidence(reinterpret_cast(subsys)); + } /*provide*/ + + /* throw exception if there's anything left for .initialize_all() to do, + * or subsystems have been added since last call to .initialize_all() + * Can use this to remind application author to call SubsystemImpl::initialize_all() + */ + static bool verify_all_initialized(); + + /* 1. initialize all subsystems: promise that for every preceding call + * to .require(), the corresponding initialization function has been + * run exactly once. + * 2. harmless to call this multiple times -- will not call any init_fn more than once + * 3. can interleave .initialize_all() with .require() as desired + */ + static InitEvidence initialize_all(); + + bool require_flag() const { return require_flag_; } + bool init_flag() const { return init_flag_; } + std::string_view subsys_name() const { return subsys_name_; } + + InitEvidence initialize(); + + private: + /* helper for .provide() */ + static void provide_aux(std::string_view subsys_name, + std::function init_fn, + SubsystemImpl * p_subsys); + + private: + /* set to true iff .s_subsys_l has been extended since last call to .initialize_all() */ + static bool s_dirty_flag; + /* one member for each unique call to .require() */ + static std::list s_subsys_l; + + private: + /* set to true on 1st call to .require() */ + bool require_flag_ = false; + /* set to true when .init_fn() invoked */ + bool init_flag_ = false; + /* unique subsystem name */ + std::string_view subsys_name_; + /* call this function once (at most) to initialize this subsystem */ + std::function init_fn_; + }; /*SubsystemImpl*/ + + template + bool + SubsystemImpl::s_dirty_flag = false; + + template + std::list *> + SubsystemImpl::s_subsys_l; + + template + void + SubsystemImpl::provide_aux(std::string_view subsys_name, + std::function init_fn, + SubsystemImpl * p_subsys) + { + if (!p_subsys->require_flag()) { + /* 1st call to .provide() for this SubsystemTag */ + + using xo::scope; + using xo::xtag; + + scope log(XO_ENTER0(chatty), + xtag("subsys", subsys_name), + xtag("address", p_subsys)); + + *p_subsys = SubsystemImpl(true /*require_flag*/, + subsys_name, + init_fn); + + s_dirty_flag = true; + s_subsys_l.push_back(p_subsys); + } + } /*provide_aux*/ + + template + bool + SubsystemImpl::verify_all_initialized() + { + if (s_dirty_flag) { + scope log(XO_ENTER0(error), "required subsystems NOT initialized!?"); + + for (SubsystemImpl * subsys : s_subsys_l) { + if (!subsys->init_flag()) { + log && log("missing InitSubsyssubsys_name(), "_tag>::require()"); + } + } + + throw std::runtime_error("Subsystem::verify_initialized:" + " Subsystem::initialize_all() never called, or out-of-date"); + return false; + } + + return true; + } /*verify_all_initialized*/ + + template + InitEvidence + SubsystemImpl::initialize_all() { + scope log(XO_ENTER0(chatty)); + + InitEvidence retval; + + if (s_dirty_flag) { + for (SubsystemImpl * subsys : s_subsys_l) { + log && log("init", xtag("subsys", subsys->subsys_name())); + + retval ^= subsys->initialize(); + } + } + + s_dirty_flag = false; + + return retval; + } /*initialize_all*/ + + template + InitEvidence + SubsystemImpl::initialize() + { + if (!init_flag_) { + init_flag_ = true; + + init_fn_(); + } + + return InitEvidence(reinterpret_cast(this)); + } /*initialize*/ + + using Subsystem = SubsystemImpl; +} /*namespace xo*/ + +/* end Subsystem.hpp */ From 539917e24c2c3985b89594947bc033b3df30848b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 15:34:08 -0400 Subject: [PATCH 0026/2524] subsys: README --- README.md | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b43d50c1..b7701781 100644 --- a/README.md +++ b/README.md @@ -1 +1,101 @@ -# subsys repo +# plugin initialization support + +subsys is a small header-only library providing support for plugin initialization + +## Features + +- provide application control of initialization order across c++ libraries +- circumvents the 'static order initialization fiasco' +- ensure initialization code runs exactly once if subsystem is linked +- enforce initialization order constraints +- defend against static linker stripping essential initialization code +- designed to work cleanly for libraries integrating into existing executable like python, java runtime, .. +- initialization state browseable at runtime + +## Getting Started + +### build + install `indentlog` dependency + +see [github/rconybea/indentlog](https://github.com/Rconybea/indentlog) + +### copy `subsys` repository locally +``` +$ git clone git@github.com:rconybea/subsys.git +$ ls -d subsys +subsys +``` + +### build + install +``` +$ cd subsys +$ mkdir build +$ cd build +$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` + +`CMAKE_PREFIX_PATH` should point to prefix where `indentlog` is installed + +alternatively, if you're a nix user: +``` +$ git clone git@github.com:rconybea/xo-nix.git +$ ls -d xo-nix +xo-nix +$ cd xo-nix +$ nix-build -A subsys +``` + +### build for unit test coverage +``` +$ cd subsys +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +## Examples + +### 1 +``` +// initialization code in .hpp for a subsystem foo, that relies on related subsystem bar + +#include "subsys/Subsystem.hpp" + +enum S_foo_tag {}; /* tag to represent initialization of subsystem foo */ + +template<> +struct InitSubsys { + static void init() { + // plugin initialization for subsystem foo + } + + static InitEvidence require() { + InitEvidence retval; + + // demand initialization of dependent subsystem bar, + // before initialization subsystem foo + // + retval ^= InitSubsys::require(); + + // initialization of this subsystem foo + retval ^= Subsystem::provide("foo", &init); + + return retval; + } +}; +``` + +``` +// in application code that relies on foo (perhaps along with other subsystems), +// for example in a pybind11 module: +// +PYBIND11_MODULE(pyfoo, m) { + // include foo in initialization set + InitSubsys::require(); + // ensure foo + dependencies are initialized + Subsystem::initialize_all(); + + ... +} +``` From 1c384e1ff0a01885bdf0713be858085814b6ece6 Mon Sep 17 00:00:00 2001 From: Roland Date: Sun, 24 Sep 2023 15:36:06 -0400 Subject: [PATCH 0027/2524] Create cmake-single-platform.yml --- .github/workflows/cmake-single-platform.yml | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/cmake-single-platform.yml diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml new file mode 100644 index 00000000..28c6f783 --- /dev/null +++ b/.github/workflows/cmake-single-platform.yml @@ -0,0 +1,39 @@ +# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml +name: CMake on a single platform + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} + From a29faaa1ae1bdebded9b594f7d91cf88ce147aad Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 17:31:32 -0400 Subject: [PATCH 0028/2524] initial commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..27fe0373 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# reflection library From 064659e17945a63ac3753f4020dcc82c184144bb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 24 Sep 2023 19:41:40 -0400 Subject: [PATCH 0029/2524] + submodule indentlog --- .gitmodules | 6 ++++++ repo/indentlog | 1 + 2 files changed, 7 insertions(+) create mode 100644 .gitmodules create mode 160000 repo/indentlog diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..698a44d5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "repo/indentlog"] + path = repo/indentlog + url = git@github.com:Rconybea/indentlog.git +[submodule "repo/refcnt/refcnt"] + path = repo/refcnt/refcnt + url = git@github.com:Rconybea/refcnt.git diff --git a/repo/indentlog b/repo/indentlog new file mode 160000 index 00000000..bf92724a --- /dev/null +++ b/repo/indentlog @@ -0,0 +1 @@ +Subproject commit bf92724aa5d123b6acb9b00318e579828fb9ff38 From 2e63293139be22e487ef82a6f20fefcf1bdd6608 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 25 Sep 2023 17:47:13 -0400 Subject: [PATCH 0030/2524] + git submodules {subsys, refcnt} --- .gitmodules | 11 +++++++---- repo/refcnt | 1 + repo/subsys | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) create mode 160000 repo/refcnt create mode 160000 repo/subsys diff --git a/.gitmodules b/.gitmodules index 698a44d5..ebcf3a27 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,9 @@ [submodule "repo/indentlog"] - path = repo/indentlog - url = git@github.com:Rconybea/indentlog.git -[submodule "repo/refcnt/refcnt"] - path = repo/refcnt/refcnt + path = repo/indentlog + url = git@github.com:Rconybea/indentlog.git +[submodule "repo/refcnt"] + path = repo/refcnt url = git@github.com:Rconybea/refcnt.git +[submodule "repo/subsys"] + path = repo/subsys + url = git@github.com:Rconybea/subsys.git diff --git a/repo/refcnt b/repo/refcnt new file mode 160000 index 00000000..4b3c854f --- /dev/null +++ b/repo/refcnt @@ -0,0 +1 @@ +Subproject commit 4b3c854fc7872ba404ce4e0af25ecfdc47822ae1 diff --git a/repo/subsys b/repo/subsys new file mode 160000 index 00000000..1c384e1f --- /dev/null +++ b/repo/subsys @@ -0,0 +1 @@ +Subproject commit 1c384e1ff0a01885bdf0713be858085814b6ece6 From fdb4ca37f44105d8062f31a3d1dcffeaaf3c0efc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 25 Sep 2023 17:49:42 -0400 Subject: [PATCH 0031/2524] reflect: initial implementation --- CMakeLists.txt | 184 +++++++ FILESYSTEM | 1 + cmake/code-coverage.cmake | 678 ++++++++++++++++++++++++ cmake/cxx.cmake | 98 ++++ cmake/reflectConfig.cmake.in | 4 + cmake/run-external-ctest | 8 + include/reflect/CMakeLists.txt | 32 ++ include/reflect/EstablishTypeDescr.hpp | 62 +++ include/reflect/Metatype.hpp | 38 ++ include/reflect/Reflect.hpp | 235 ++++++++ include/reflect/SelfTagging.hpp | 31 ++ include/reflect/StructReflector.hpp | 161 ++++++ include/reflect/TaggedPtr.hpp | 125 +++++ include/reflect/TaggedRcptr.hpp | 88 +++ include/reflect/TypeDescr.hpp | 302 +++++++++++ include/reflect/TypeDescrExtra.hpp | 65 +++ include/reflect/TypeDrivenMap.hpp | 47 ++ include/reflect/atomic/AtomicTdx.hpp | 37 ++ include/reflect/init_reflect.hpp | 22 + include/reflect/pointer/PointerTdx.hpp | 76 +++ include/reflect/struct/StructMember.hpp | 236 +++++++++ include/reflect/struct/StructTdx.hpp | 94 ++++ include/reflect/vector/VectorTdx.hpp | 100 ++++ repo/README.md | 5 + src/reflect/CMakeLists.txt | 52 ++ src/reflect/TaggedRcptr.cpp | 30 ++ src/reflect/TypeDescr.cpp | 194 +++++++ src/reflect/TypeDescrExtra.cpp | 37 ++ src/reflect/atomic/AtomicTdx.cpp | 24 + src/reflect/init_reflect.cpp | 23 + src/reflect/pointer/PointerTdx.cpp | 17 + src/reflect/struct/StructMember.cpp | 36 ++ src/reflect/struct/StructTdx.cpp | 55 ++ src/reflect/vector/VectorTdx.cpp | 20 + utest/CMakeLists.txt | 57 ++ utest/StructReflector.test.cpp | 142 +++++ utest/StructTdx.test.cpp | 59 +++ utest/VectorTdx.test.cpp | 181 +++++++ utest/reflect_utest_main.cpp | 6 + 39 files changed, 3662 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 FILESYSTEM create mode 100644 cmake/code-coverage.cmake create mode 100644 cmake/cxx.cmake create mode 100644 cmake/reflectConfig.cmake.in create mode 100755 cmake/run-external-ctest create mode 100644 include/reflect/CMakeLists.txt create mode 100644 include/reflect/EstablishTypeDescr.hpp create mode 100644 include/reflect/Metatype.hpp create mode 100644 include/reflect/Reflect.hpp create mode 100644 include/reflect/SelfTagging.hpp create mode 100644 include/reflect/StructReflector.hpp create mode 100644 include/reflect/TaggedPtr.hpp create mode 100644 include/reflect/TaggedRcptr.hpp create mode 100644 include/reflect/TypeDescr.hpp create mode 100644 include/reflect/TypeDescrExtra.hpp create mode 100644 include/reflect/TypeDrivenMap.hpp create mode 100644 include/reflect/atomic/AtomicTdx.hpp create mode 100644 include/reflect/init_reflect.hpp create mode 100644 include/reflect/pointer/PointerTdx.hpp create mode 100644 include/reflect/struct/StructMember.hpp create mode 100644 include/reflect/struct/StructTdx.hpp create mode 100644 include/reflect/vector/VectorTdx.hpp create mode 100644 repo/README.md create mode 100644 src/reflect/CMakeLists.txt create mode 100644 src/reflect/TaggedRcptr.cpp create mode 100644 src/reflect/TypeDescr.cpp create mode 100644 src/reflect/TypeDescrExtra.cpp create mode 100644 src/reflect/atomic/AtomicTdx.cpp create mode 100644 src/reflect/init_reflect.cpp create mode 100644 src/reflect/pointer/PointerTdx.cpp create mode 100644 src/reflect/struct/StructMember.cpp create mode 100644 src/reflect/struct/StructTdx.cpp create mode 100644 src/reflect/vector/VectorTdx.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/StructReflector.test.cpp create mode 100644 utest/StructTdx.test.cpp create mode 100644 utest/VectorTdx.test.cpp create mode 100644 utest/reflect_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..4f777c59 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,184 @@ +# reflect/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(reflect VERSION 0.1) +enable_language(CXX) + +include(cmake/cxx.cmake) +include(cmake/code-coverage.cmake) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) +add_code_coverage() +# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc. +# we're not interested in code coverage for these sources. +# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves; +# rather, want coverage on the code that the unit tests exercise. +# +# NOTE: this seems to work only with the 'ccov-all' target. In particular, doesn't seem to do anything with the 'ccov' target +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) + +# ---------------------------------------------------------------- +# c++ settings + +set(XO_PROJECT_NAME reflect) +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") + +add_definitions(${PROJECT_CXX_FLAGS}) + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) +endif() + +set(CMAKE_CXX_STANDARD_REQUIRED True) + +# always write compile_commands.json +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") + +# ---------------------------------------------------------------- +# external projects (need these to exist before add_subdirectory() below) +# +# we are expecting these projects to coexist peacefully in build/local +# (i.e. can run their `make install` steps independently with prefix build/local, +# without any collisions) +# + +include(ExternalProject) + +## ----- indentlog ------ + +# NOTE: we could have cmake handle git interaction, +# but we want source for certain dependencies to live in a location +# that's suitable for accepting changes + coordinated commits. +# In particular, not in the build directory! +# +externalproject_add( + project_indentlog + SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/indentlog + BINARY_DIR ${PROJECT_BINARY_DIR}/ext/indentlog + INSTALL_DIR ${PROJECT_BINARY_DIR}/local + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= + BUILD_COMMAND make + INSTALL_COMMAND make install + TEST_BEFORE_INSTALL True +) + +add_library(indentlog INTERFACE IMPORTED) +#set_property(TARGET indentlog PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/local/lib/libindentlog.so) +add_dependencies(indentlog project_indentlog) + +# runs ctest in indentlog build dir +add_test(NAME indentlog COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/indentlog) +#target_code_coverage(indentlog EXTERNAL AUTO ALL) + +# ----- subsys ----- + +externalproject_add( + project_subsys + SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/subsys + BINARY_DIR ${PROJECT_BINARY_DIR}/ext/subsys + INSTALL_DIR ${PROJECT_BINARY_DIR}/local + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= + BUILD_COMMAND make + INSTALL_COMMAND make install + TEST_BEFORE_INSTALL True +) + +add_library(subsys INTERFACE IMPORTED) +add_dependencies(subsys project_subsys) + +# runs ctest in subsys build dir +add_test(NAME subsys COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/subsys) + +# ----- refcnt ----- + +# CMAKE_ARGS +# CMAKE_BUILD_TYPE propagate Debug/Release build type +# CODE_COVERAGE propagate code coverage setting +# CMAKE_PREFIX_PATH path for support cmake files of dependencies (needed for find_package() to work) +# CMAKE_INSTALL_PREFIX install subproject here +# SOURCE_DIR -- where to find already established source code +# BINARY_DIR -- run build for external project here +# INSTALL_DIR -- (temporarily) install external project here +# +externalproject_add( + project_refcnt + SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/refcnt + BINARY_DIR ${PROJECT_BINARY_DIR}/ext/refcnt + INSTALL_DIR ${PROJECT_BINARY_DIR}/local + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= + BUILD_COMMAND make + INSTALL_COMMAND make install + TEST_BEFORE_INSTALL True +) + +add_library(refcnt SHARED IMPORTED) +set_property(TARGET refcnt PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/local/lib/librefcnt.so) +add_dependencies(refcnt project_refcnt) +add_dependencies(refcnt project_indentlog) + +# runs ctest in refcnt build dir +add_test(NAME refcnt COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/refcnt) + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/reflect) +add_subdirectory(utest) + +# ---------------------------------------------------------------- +# cmake export: +# +# populate .cmake files in $CMAKE_INSTALL_LIBDIR/cmake/reflect. +# cmake projects that include this directory in $CMAKE_PREFIX_PATH +# can use +# find_package(reflect REQUIRED) +# and +# target_link_libraries(${sometarget} PUBLIC reflect) +# to use the reflect library + +set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") +set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") + +include(CMakePackageConfigHelpers) + +# generates build/reflectConfigVersion.cmake +write_basic_package_version_file( + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + VERSION 0.1 + COMPATIBILITY AnyNewerVersion +) + +# generates build/reflectConfig.cmake +configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/${XO_PROJECT_NAME}Config.cmake.in" + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" + INSTALL_DESTINATION lib/cmake/${XO_PROJECT_NAME} +) + +# creates {reflectTargets.cmake, reflectTargets-noconfig.cmake} in $CMAKE_INSTALL_LIBDIR/cmake/reflect/ +# requires +# install(.. EXPORT reflectTargets ..) +# +install( + EXPORT ${XO_PROJECT_NAME}Targets + DESTINATION lib/cmake/${XO_PROJECT_NAME} +) + +# creates {reflectConfigVersion.cmake, reflectConfig.cmake} in $CMAKE_INSTALL_LIBDIR/cmake/reflect/ +install( + FILES + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" + "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" + DESTINATION lib/cmake/${XO_PROJECT_NAME}) + +# ---------------------------------------------------------------- +# install .hpp files + +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/reflect/ DESTINATION include/reflect) diff --git a/FILESYSTEM b/FILESYSTEM new file mode 100644 index 00000000..9af86a7a --- /dev/null +++ b/FILESYSTEM @@ -0,0 +1 @@ +repo -- git submoduules here diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake new file mode 100644 index 00000000..b6b36064 --- /dev/null +++ b/cmake/code-coverage.cmake @@ -0,0 +1,678 @@ +# +# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# USAGE: To enable any code coverage instrumentation/targets, the single CMake +# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or +# on the command line. +# +# From this point, there are two primary methods for adding instrumentation to +# targets: +# +# 1 - A blanket instrumentation by calling `add_code_coverage()`, where +# all targets in that directory and all subdirectories are automatically +# instrumented. +# +# 2 - Per-target instrumentation by calling +# `target_code_coverage()`, where the target is given and thus only +# that target is instrumented. This applies to both libraries and executables. +# +# To add coverage targets, such as calling `make ccov` to generate the actual +# coverage information for perusal or consumption, call +# `target_code_coverage()` on an *executable* target. +# +# Example 1: All targets instrumented +# +# In this case, the coverage information reported will will be that of the +# `theLib` library target and `theExe` executable. +# +# 1a: Via global command +# +# ~~~ +# add_code_coverage() # Adds instrumentation to all targets +# +# add_library(theLib lib.cpp) +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target +# # (instrumentation already added via global anyways) +# # for generating code coverage reports. +# ~~~ +# +# 1b: Via target commands +# +# ~~~ +# add_library(theLib lib.cpp) +# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. +# ~~~ +# +# Example 2: Target instrumented, but with regex pattern of files to be excluded +# from report +# +# ~~~ +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ +# +# Example 3: Target added to the 'ccov' and 'ccov-all' targets +# +# ~~~ +# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. +# +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ + +# Options +option( + CODE_COVERAGE + "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" + OFF) + +# Programs +find_program(LLVM_COV_PATH llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) +# Hide behind the 'advanced' mode flag for GUI/ccmake +mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) + +# Variables +set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) +set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) + +# Common initialization/checks +if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) + set(CODE_COVERAGE_ADDED ON) + + # Common Targets + add_custom_target( + ccov-preprocessing + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} + DEPENDS ccov-clean) + + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + # Messages + message(STATUS "Building with llvm Code Coverage Tools") + + if(NOT LLVM_COV_PATH) + message(FATAL_ERROR "llvm-cov not found! Aborting.") + else() + # Version number checking for 'EXCLUDE' compatibility + execute_process(COMMAND ${LLVM_COV_PATH} --version + OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION + ${LLVM_COV_VERSION_CALL_OUTPUT}) + + if(LLVM_COV_VERSION VERSION_LESS "7.0.0") + message( + WARNING + "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" + ) + endif() + endif() + + # Targets + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + else() + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + endif() + + # Used to get the shared object file list before doing the main all- + # processing + add_custom_target( + ccov-libs + COMMAND ; + COMMENT "libs ready for coverage report.") + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + # Messages + message(STATUS "Building with lcov Code Coverage Tools") + + if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) + if(NOT ${upper_build_type} STREQUAL "DEBUG") + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + else() + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Targets + add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory + ${CMAKE_BINARY_DIR} --zerocounters) + + else() + message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") + endif() +endif() + +# Adds code coverage instrumentation to a library, or instrumentation/targets +# for an executable target. +# ~~~ +# EXECUTABLE ADDED TARGETS: +# GCOV/LCOV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# +# LLVM-COV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report. +# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. +# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. +# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. +# ccov-all-export : Exports the coverage report to a JSON file. +# +# Required: +# TARGET_NAME - Name of the target to generate code coverage for. +# Optional: +# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. +# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. +# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) +# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. +# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. +# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory +# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** +# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call +# ~~~ +function(target_code_coverage TARGET_NAME) + # Argument parsing + set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) + set(single_value_keywords COVERAGE_TARGET_NAME) + set(multi_value_keywords EXCLUDE OBJECTS ARGS) + cmake_parse_arguments( + target_code_coverage "${options}" "${single_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + # Set the visibility of target functions to PUBLIC, INTERFACE or default to + # PRIVATE. + if(target_code_coverage_PUBLIC) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY PUBLIC) + elseif(target_code_coverage_INTERFACE) + set(TARGET_VISIBILITY INTERFACE) + set(TARGET_LINK_VISIBILITY INTERFACE) + elseif(target_code_coverage_PLAIN) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY) + else() + set(TARGET_VISIBILITY PRIVATE) + set(TARGET_LINK_VISIBILITY PRIVATE) + endif() + + if(NOT target_code_coverage_COVERAGE_TARGET_NAME) + # If a specific name was given, use that instead. + set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) + endif() + + if(CODE_COVERAGE) + + # Add code coverage instrumentation to the target's linker command + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs + -ftest-coverage) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) + endif() + + # Targets + get_target_property(target_type ${TARGET_NAME} TYPE) + + # Add shared library to processing for 'all' targets + if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" >> + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + if(NOT TARGET ccov-libs) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-libs + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # If there are shared objects to also work with, generate the string to + # add them here + foreach(SO_TARGET ${target_code_coverage_OBJECTS}) + # Check to see if the target is a shared object + if(TARGET ${SO_TARGET}) + get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) + if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") + set(SO_OBJECTS ${SO_OBJECTS} -object=$) + endif() + endif() + endforeach() + + # Run the executable, generating raw profile data Make the run data + # available for further processing. Separated to allow Windows to run + # this target serially. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list + JOB_POOL ccov_serial_pool + DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) + + # Merge the generated profile data so llvm-cov can process it + add_custom_target( + ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_PROFDATA_PATH} merge -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Ignore regex only works on LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print out details of the coverage information to the command line + add_custom_target( + ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Print out a summary of the coverage information to the command line + add_custom_target( + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) + + # Run the executable, generating coverage information + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND $ ${target_code_coverage_ARGS} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + # Generate exclusion string for use + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + if(NOT ${target_code_coverage_EXTERNAL}) + set(EXTERNAL_OPTION --no-external) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + else() + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + endif() + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + + add_custom_command( + TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." + ) + + # AUTO + if(target_code_coverage_AUTO) + if(NOT TARGET ccov) + add_custom_target(ccov) + endif() + add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) + + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) + endif() + add_dependencies( + ccov-report + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # ALL + if(target_code_coverage_ALL) + if(NOT TARGET ccov-all-processing) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-all-processing + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + endif() +endfunction() + +# Adds code coverage instrumentation to all targets in the current directory and +# any subdirectories. To add coverage instrumentation to only specific targets, +# use `target_code_coverage`. +function(add_code_coverage) + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_compile_options(-fprofile-instr-generate -fcoverage-mapping) + add_link_options(-fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options(-fprofile-arcs -ftest-coverage) + link_libraries(gcov) + endif() + endif() +endfunction() + +# Adds the 'ccov-all' type targets that calls all targets added via +# `target_code_coverage` with the `ALL` parameter, but merges all the coverage +# data from them into a single large report instead of the numerous smaller +# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for +# use with coverage dashboards (e.g. codecov.io, coveralls). +# ~~~ +# Optional: +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! +# ~~~ +function(add_code_coverage_all_targets) + # Argument parsing + set(multi_value_keywords EXCLUDE) + cmake_parse_arguments(add_code_coverage_all_targets "" "" + "${multi_value_keywords}" ${ARGN}) + + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # Merge the profile data for all of the run executables + if(WIN32) + add_custom_target( + ccov-all-processing + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe + merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -sparse $$FILELIST) + else() + add_custom_target( + ccov-all-processing + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + endif() + + # Regex exclude only available for LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print summary of the code coverage information to the command line + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-all-export + COMMAND + ${LLVM_COV_PATH} export `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + + # Generate HTML output of all added targets for perusal + if(WIN32) + add_custom_target( + ccov-all + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") + + # Nothing required for gcov + add_custom_target(ccov-all-processing COMMAND ;) + + # Exclusion regex string creation + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + else() + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + endif() + + # Generates HTML output of all targets for perusal + add_custom_target( + ccov-all + COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} + DEPENDS ccov-all-capture) + + endif() + + add_custom_command( + TARGET ccov-all + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." + ) + endif() +endfunction() diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake new file mode 100644 index 00000000..fa6efb89 --- /dev/null +++ b/cmake/cxx.cmake @@ -0,0 +1,98 @@ +# ---------------------------------------------------------------- +# use this in subdirs that compile c++ code +# +macro(xo_include_options target) + # ---------------------------------------------------------------- + # PROJECT_SOURCE_DIR: + # so we can for example write + # #include "ordinaltree/foo.hpp" + # from anywhere in the project + # PROJECT_BINARY_DIR: + # since generated version file will be in build directory, + # need that build directory to also appear in + # compiler's include path + # + target_include_directories( + ${target} PUBLIC + $ # e.g. for #include "indentlog/scope.hpp" + $ + $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ + $ # e.g. for generated config.hpp file + ) + + # ---------------------------------------------------------------- + # make standard directories for std:: includes explicit + # so that + # (1) they appear in compile_commands.json. + # (2) clangd (run from emacs lsp-mode) can find them + # + if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) + endif() +endmacro() + +# ---------------------------------------------------------------- +# variable +# XO_ADDRESS_SANITIZE +# determines whether to enable address sanitizer for the XO project +# (see toplevel CMakeLists.txt) +# ---------------------------------------------------------------- +if(XO_ADDRESS_SANITIZE) + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() + +# XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF +set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) + +# XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON +# +# address sanitizer build complains about _FORTIFY_SOURCE redefines +# In file included from :460: +# :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] +# #define _FORTIFY_SOURCE 2 +# +set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) + +# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro +if(XO_ADDRESS_SANITIZE) + set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) +else() + set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) +endif() + +# ---------------------------------------------------------------- +# generally want all the errors+warnings! +# however: address sanitizer generates error on _FORTIFY_SOURCE +# +macro(xo_compile_options target) + target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) +endmacro() + +# ---------------------------------------------------------------- +# use this for a subdir that builds a library +# EXPORT drives .cmake config files intended for consumption +# by higher-level cmake projects via find_package() +# +macro(xo_install_library target) + install( + TARGETS ${target} + EXPORT ${target}Targets + LIBRARY DESTINATION lib COMPONENT Runtime + ARCHIVE DESTINATION lib COMPONENT Development + RUNTIME DESTINATION bin COMPONENT Runtime + PUBLIC_HEADER DESTINATION include COMPONENT Development + BUNDLE DESTINATION bin COMPONENT Runtime + ) +endmacro() + +# ---------------------------------------------------------------- +# use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers +# +macro(xo_indentlog_dependency target) + find_package(indentlog REQUIRED) + #add_dependencies(${target} indentlog) + target_link_libraries(${target} PUBLIC indentlog) + #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) +endmacro() diff --git a/cmake/reflectConfig.cmake.in b/cmake/reflectConfig.cmake.in new file mode 100644 index 00000000..e13a2c54 --- /dev/null +++ b/cmake/reflectConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/cmake/run-external-ctest b/cmake/run-external-ctest new file mode 100755 index 00000000..386c45a4 --- /dev/null +++ b/cmake/run-external-ctest @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# $1 = build directory + +cd $1 +shift + +ctest "${@}" diff --git a/include/reflect/CMakeLists.txt b/include/reflect/CMakeLists.txt new file mode 100644 index 00000000..a7e07dd6 --- /dev/null +++ b/include/reflect/CMakeLists.txt @@ -0,0 +1,32 @@ +# reflect/CMakeLists.txt + +set(SELF_LIBRARY_NAME reflect) + +# build shared library 'reflect' +add_library(${SELF_LIBRARY_NAME} SHARED TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp atomic/AtomicTdx.cpp pointer/PointerTdx.cpp vector/VectorTdx.cpp struct/StructTdx.cpp struct/StructMember.cpp init_reflect.cpp) + +set_target_properties(${SELF_LIBRARY_NAME} PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION 1 + PUBLIC_HEADER TypeDescr.hpp) + +# ---------------------------------------------------------------- +# all the errors+warnings! +# +#target_compile_options(${SELF_LIBRARY_NAME} PRIVATE -Werror -Wall -Wextra) +xo_compile_options(${SELF_LIBRARY_NAME}) +xo_include_options(${SELF_LIBRARY_NAME}) + +# ---------------------------------------------------------------- +# internal dependencies: logutil, ... + +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) + +# ---------------------------------------------------------------- +# 3rd party dependency: boost: + +#xo_boost_dependency(${SELF_LIBRARY_NAME}) + +xo_install_library(${SELF_LIBRARY_NAME}) + +# end CMakeLists.txt diff --git a/include/reflect/EstablishTypeDescr.hpp b/include/reflect/EstablishTypeDescr.hpp new file mode 100644 index 00000000..2b0e25a7 --- /dev/null +++ b/include/reflect/EstablishTypeDescr.hpp @@ -0,0 +1,62 @@ +/* file EstablishTypeDescr.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "reflect/TypeDescr.hpp" +#include "reflect/TaggedPtr.hpp" + +namespace xo { + namespace reflect { + class EstablishTypeDescr { + public: + /* implementation method; expect this to be used only within reflect/ library. + * avoids some otherwise-cyclic #include paths + * between specialized headers such as vector/VectorTdx.hpp and this + * EstablishTypeDescr.hpp + */ +#ifdef OBSOLETE + template + static TaggedPtr establish_tp(T * x) { return TaggedPtr(establish(), x); } +#endif + template + static TaggedPtr establish_most_derived_tp(T * x) { return establish()->most_derived_self_tp(x); } + + template + static TypeDescrW establish() { + TypeDescrW td = TypeDescrBase::require(&typeid(T), + type_name(), + nullptr); + +#ifdef NOT_USING + std::function to_self_tp; + + if (std::is_base_of_v) { + /* T is a descendant of SelfTagging (or T = SelfTagging); + * use SelfTagging.self_tp() + */ + to_self_tp = [](void * x) { return reinterpret_cast(x)->self_tp(); }; + } else { + /* T is not a descendant of SelfTagging. + * want to return + */ + to_self_tp = [td](void * x) { return TaggedPtr(td, x); }; + } + + td->assign_to_self_tp(to_self_tp); +#endif + return td; + } + }; /*EstablishTypeDescr*/ + + template + inline TaggedPtr establish_most_derived_tp(T * x) { + return EstablishTypeDescr::establish_most_derived_tp(x); + } + } /*namespace reflect*/ +} /*namespace xo*/ + + +/* end EstablishTypeDescr.hpp */ diff --git a/include/reflect/Metatype.hpp b/include/reflect/Metatype.hpp new file mode 100644 index 00000000..23f3fd10 --- /dev/null +++ b/include/reflect/Metatype.hpp @@ -0,0 +1,38 @@ +/* @file Metatype.hpp */ + +#pragma once + +#include + +namespace xo { + namespace reflect { + enum class Metatype { mt_invalid, mt_atomic, mt_pointer, mt_vector, mt_struct }; + + inline std::ostream & operator<<(std::ostream & os, + Metatype x) { + switch(x) { + case Metatype::mt_invalid: + os << "invalid!"; + break; + case Metatype::mt_atomic: + os << "atomic"; + break; + case Metatype::mt_pointer: + os << "pointer"; + break; + case Metatype::mt_vector: + os << "vector"; + break; + case Metatype::mt_struct: + os << "struct"; + break; + default: + os << "???"; + } + return os; + } /*operator<<*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end Metatype.hpp */ diff --git a/include/reflect/Reflect.hpp b/include/reflect/Reflect.hpp new file mode 100644 index 00000000..73ef68f3 --- /dev/null +++ b/include/reflect/Reflect.hpp @@ -0,0 +1,235 @@ +/* file Reflect.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "reflect/SelfTagging.hpp" +#include "reflect/EstablishTypeDescr.hpp" +#include "reflect/atomic/AtomicTdx.hpp" +#include "reflect/pointer/PointerTdx.hpp" +#include "reflect/vector/VectorTdx.hpp" +#include "reflect/struct/StructTdx.hpp" +#include "refcnt/Refcounted.hpp" +#include +#include +#include // for std::pair<> + +namespace xo { + namespace reflect { + template + class EstablishTdx { + public: + static std::unique_ptr make() { return AtomicTdx::make(); } + }; /*EstablishTdx*/ + + // ----- xo::ref::rp ----- + + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ + + // ----- std::array ----- + + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ + + // ----- std::vector ----- + + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ + + // ----- std::pair ----- + + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ + + // ----- MakeTagged ----- + + template + class TaggedPtrMaker { + public: + static TaggedPtr make_tp(T * x); + static TaggedRcptr make_rctp(T * x); + }; + + template<> + class TaggedPtrMaker { + public: + static TaggedPtr make_tp(SelfTagging * x) { + return x->self_tp(); + } /*make_tp*/ + + static TaggedRcptr make_rctp(SelfTagging * x) { + return x->self_tp(); + } /*make_rctp*/ + }; /*TaggedPtrMaker*/ + + // ----- Reflect ----- + + class Reflect { + public: + /* Use: + * using mytype = ...; + * if (Reflect::is_reflected()) { ... } + */ + template + static bool is_reflected() { return TypeDescrBase::is_reflected(&typeid(T)); } + + /* Use: + * using mytype = ...; + * TypeDescrW td = Reflect::require(); + * + * Note: + * To avoid cyclic header dependencies + * (between EstablishTypeDescr.hpp <-> {vector/VectorTdx.hpp etc.}, + * we use a 2-stage setup process: + * + * 1. EstablishTypeDescr::establish() creates a TypeDescr* object + * with lowest-common-denominator .tdextra AtomicTdx. + * (see [reflect/EstablishTypeDescr.hpp]) + * + * 2. Reflect::require() upgrades .tdextra to suitable implementation + * depending on T; this means also need to visit reflection info + * (TypeDescr objects) for nested types to upgrade them too. + * + * This allows template-fu for a compound type (like std::vector), + * implemented in specialized header (like [reflect/struct/VectorTdx.hpp]) to + * refer to reflection info for T without having to pull in all the + * headers needed to properly reflect T (like this [reflect/Reflect.hpp]) + * + */ + template + static TypeDescrW require() { + TypeDescrW retval_td = EstablishTypeDescr::establish(); + + /* mark TypeDescr for T as complete (even though it isn't quite yet), + * so that when we encounter recursive types, reflection terminates. + * For example consider type resulting from code like + * + * typename T; + * using T = std::vector; + * + */ + if (retval_td->mark_complete()) { + /* control here on 2nd+later calls to require(). + * in principle can immediately short-circuit. + */ + } else { + /* control comes here the first time require() runs */ + + auto final_tdx = EstablishTdx::make(); + + retval_td->assign_tdextra(std::move(final_tdx)); + + /* also need to require for each child */ + } + + return retval_td; + } /*require*/ + + /* Use: + * T * xyz = ...; + * TaggedPtr xyz_tp = Reflect::make_tp(xyz); + */ + template + static TaggedPtr make_tp(T * x) { return TaggedPtrMaker::make_tp(x); } + + template + static TaggedRcptr make_rctp(T * x) { return TaggedPtrMaker::make_rctp(x); } + }; /*Reflect*/ + + // ----- MakeTagged ----- + + template + TaggedPtr + TaggedPtrMaker::make_tp(T * x) { + return TaggedPtr(Reflect::require(), x); + } /*make_tp*/ + + template + TaggedRcptr + TaggedPtrMaker::make_rctp(T * x) { + return TaggedRcptr(Reflect::require(), x); + } /*make_rctp*/ + + // ----- xo::ref::rp ----- + + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Object is property reflected. + * + * In practice must be a class type, since has to store refcount + * + supply assoc'd incr/decr methods + */ + Reflect::require(); + + return RefPointerTdx>::make(); + } /*make*/ + + // ----- std::array ----- + + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Element is properly reflected */ + Reflect::require(); + + return StdArrayTdx::make(); + } /*make*/ + + // ----- std::vector ----- + + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Element is properly reflected */ + Reflect::require(); + + return StdVectorTdx::make(); + } /*make*/ + + // ----- std::pair ----- + + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Lhs, Rhs are properly reflected */ + Reflect::require(); + Reflect::require(); + + return StructTdx::pair(); + } /*make*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end Reflect.hpp */ diff --git a/include/reflect/SelfTagging.hpp b/include/reflect/SelfTagging.hpp new file mode 100644 index 00000000..29517e33 --- /dev/null +++ b/include/reflect/SelfTagging.hpp @@ -0,0 +1,31 @@ +/* file SelfTagging.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "refcnt/Refcounted.hpp" +#include "reflect/TypeDescr.hpp" +#include "reflect/TaggedRcptr.hpp" + +namespace xo { + namespace reflect { + /* a self-tagging object uses reflection to preserve type information + * until runtime. Can use the reflected information to traverse + * object representation (e.g. for printing / serialization) + * without repetitive/bulky boilerplate. + * + * For pybind11 need to have concrete (non-template) apis, + * helpful to have various classes inherit SelfTagging + * + * For example see [printjson/PrintJson.hpp] + */ + class SelfTagging : public ref::Refcount { + public: + virtual TaggedRcptr self_tp() = 0; + }; /*SelfTagging*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end SelfTagging.hpp */ diff --git a/include/reflect/StructReflector.hpp b/include/reflect/StructReflector.hpp new file mode 100644 index 00000000..f2237578 --- /dev/null +++ b/include/reflect/StructReflector.hpp @@ -0,0 +1,161 @@ +/* @file StructReflector.hpp */ + +#pragma once + +#include "reflect/Reflect.hpp" +#include "reflect/TypeDescr.hpp" +#include "reflect/struct/StructMember.hpp" +#include "reflect/struct/StructTdx.hpp" +#include + +namespace xo { + namespace reflect { + template + class SelfTagger {}; + + template + struct SelfTagger { + static TaggedPtr self_tp(void * object) { + return (reinterpret_cast(object))->self_tp(); + } + }; + + template + struct SelfTagger { + static TaggedPtr self_tp(void * /*object*/) { assert(false); return TaggedPtr::universal_null(); } + }; + + /* RAII pattern for reflecting a struct. + * + * Use: + * struct Foo { int x_; double y_; }; + * + * StructReflector sr; + * REFLECT_LITERAL_MEMBER(sr, x_); + * REFLECT_LITERAL_MEMBER(sr, y_); + * + * // optional: regardless, reflection will be completed when sr goes out of scope + * sr.require_complete(); + */ + template + class StructReflector { + public: + using struct_t = StructT; + + public: + StructReflector() : td_{EstablishTypeDescr::establish()} {} + ~StructReflector() { + this->require_complete(); + } + + bool is_complete() const { return s_reflected_flag; } + bool is_incomplete() const { return !s_reflected_flag; } + + template + void reflect_member(std::string const & member_name, + MemberT OwnerT::* member_addr) { + + auto accessor + (GeneralStructMemberAccessor::make(member_addr)); + + /* used to do this in GeneralStructMemberAccessor<> ctor, + * but that introduces #include cycle + */ + Reflect::require(); + + this->member_v_.emplace_back(member_name, std::move(accessor)); + } /*reflect_member*/ + + void require_complete() { + if(!s_reflected_flag) { + s_reflected_flag = true; + + constexpr bool have_to_self_tp = std::is_base_of_v; + + /* if self-tagging, can use .self_tp() to get most-derived tagged pointer */ + auto to_self_tp_fn + = ([](void * object) + { + return SelfTagger::self_tp(object); + }); + + auto tdx = StructTdx::make(std::move(this->member_v_), + have_to_self_tp, + to_self_tp_fn); + + this->td_->assign_tdextra(std::move(tdx)); + } + } /*complete*/ + + template + void adopt_ancestors() { + assert(Reflect::is_reflected()); + + TypeDescr ancestor_td = Reflect::require(); + + /* requires that reflection of AncestorT has completed */ + { + assert(ancestor_td->is_struct()); + assert(ancestor_td->complete_flag()); + } + + /* for structs, + * we know that object argument to TypeDescr::n_child() is unused + */ + for (uint32_t i = 0, n = ancestor_td->n_child(nullptr); i < n; ++i) { + StructMember const & member = ancestor_td->struct_member(i); + + this->member_v_.push_back(member.for_descendant()); + } + } /*adopt_ancestors*/ + + private: + /* set irrevocably to true when .complete() runs. + * + * want to reflect a particular type once; + * short-circuit 2nd or later attempts on the same type + */ + static bool s_reflected_flag; + + /* type description object for StructT */ + TypeDescrW td_; + + /* members of StructT (at least those we're choosing to reflect) */ + std::vector member_v_; + }; /*StructReflector*/ + + template + bool StructReflector::s_reflected_flag = false; + } /*namespace reflect*/ + + /* e.g. + * struct Foo { int bar_; }; + * struct Bar : public Foo { .. }; + * + * StructReflector sr; + * REFLECT_EXPLICIT_MEMBER(sr, "bar", &Foo::bar_); + */ +#define REFLECT_EXPLICIT_MEMBER(sr, member_name, member) sr.reflect_member(member_name, member) + + /* e.g. + * struct Foo { int bar_; }; + * + * StructReflector sr; + * REFLECT_LITERAL_MEMBER(sr, bar_); + * + * then REFLECT_LITERAL_MEMBER() expands to something like: + * sr.reflect_member("bar_", &StructReflector::struct_t::bar_) + */ +#define REFLECT_LITERAL_MEMBER(sr, member_name) sr.reflect_member(#member_name, &decltype(sr)::struct_t::member_name) + + /* like REFLECT_LITERAL_MEMBER(), but append trailing underscore + * + * minor convenience, so we can write + * struct Foo { int bar_; }; + * + * StructReflector sr; + * REFLECT_MEMBER(sr, bar); // reflects Foo::bar_ as "bar" + */ +#define REFLECT_MEMBER(sr, member_name) sr.reflect_member(#member_name, &decltype(sr)::struct_t::member_name##_) + +} /*namespace xo*/ diff --git a/include/reflect/TaggedPtr.hpp b/include/reflect/TaggedPtr.hpp new file mode 100644 index 00000000..653c8ad7 --- /dev/null +++ b/include/reflect/TaggedPtr.hpp @@ -0,0 +1,125 @@ +/* @file TaggedPtr.hpp */ + +#pragma once + +#include "reflect/TypeDescr.hpp" +//#include "reflect/EstablishTypeDescr.hpp" +#include + +namespace xo { +namespace reflect { + class TaggedRcptr; /* see [reflect/TaggedRcptr.hpp] */ + + class TaggedPtr { + public: + TaggedPtr(TypeDescr td, void * x) : td_{td}, address_{x} {} + + static TaggedPtr universal_null() { return TaggedPtr(nullptr, nullptr); } + + /* would be clean to put make() here; + * however it leads to cyclic #include paths, + * so put it elsewhere + */ +#ifdef NOT_USING + template + static TaggedPtr make(T * x) { return TaggedPtr(Reflect::require(), x); } +#endif + + /* visit an object tree. calls preorder_visit_fn() on tp, + * and all objects reachable directly-or-indirectly from tp. + * will call preorder_visit_fn() multiple times if there are multiple paths + * to a node. + * + * require: no cycles in object graph -- undefined behavior if a cycle is present + */ + template + static void visit_tree_preorder(TaggedPtr tp, Fn && preorder_visit_fn) { + using std::uint32_t; + + preorder_visit_fn(tp); + + for(uint32_t i = 0, n = tp.n_child(); i < n; ++i) { + visit_tree_preorder(tp.get_child(i), preorder_visit_fn); + } + } /*visit_tree_preorder*/ + + /* visit object graph. calls preorder_visit_fn() on tp in depth-first + * order. detects and silently prunes duplicate/cyclic references. + */ + template + static void visit_graph(TaggedPtr tp, Fn && visit_fn) { + std::unordered_set visited_set; + + visit_graph_aux(tp, visit_fn, &visited_set); + } /*visit_graph*/ + + TypeDescr td() const { return td_; } + void * address() const { return address_; } + + void assign_td(TypeDescr x) { td_ = x; } + void assign_address(void * x) { address_ = x; } + + bool is_universal_null() const { return (td_ == nullptr) && (address_ == nullptr); } + bool is_vector() const { return td_ && td_->is_vector(); } + bool is_struct() const { return td_ && td_->is_struct(); } + + + /* returns pointer-to-T, if in fact this tagged pointer is understood + * to refer to a T-instance; otherwise nullptr + */ + template + T * recover_native() const { return this->td_->recover_native(this->address_); } + + uint32_t n_child() const { + return this->td_->n_child(this->address_); + } /*n_child*/ + + TaggedPtr get_child(uint32_t i) const { + return this->td_->child_tp(i, this->address_); + } /*get_child*/ + + /* require: + * - .is_struct() is true + */ + std::string const & struct_member_name(uint32_t i) const { + return this->td_->struct_member_name(i); + } + + private: + template + static void visit_graph_aux(TaggedPtr tp, + Fn && visit_fn, + std::unordered_set * p_visited_set) + { + if (tp.address() == nullptr) + return; + + if (p_visited_set->find(tp.address()) == p_visited_set->end()) { + p_visited_set->insert(tp.address()); + + visit_fn(tp); + + for (uint32_t i = 0, n = tp.n_child(); i < n; ++i) { + visit_graph_aux(tp.get_child(i), visit_fn, p_visited_set); + } + } + } /*visit_graph_aux*/ + + private: + friend class TaggedRcptr; + + private: + /* describes the actual type stored at *address. + * can be null if .address is null + */ + TypeDescr td_; + /* address with type information preserved at runtime */ + void * address_; + }; /*TaggedPtr*/ + +} /*namespace reflect*/ +} /*namespace xo*/ + +/* end TaggedPtr.hpp */ + + diff --git a/include/reflect/TaggedRcptr.hpp b/include/reflect/TaggedRcptr.hpp new file mode 100644 index 00000000..9ca8b15f --- /dev/null +++ b/include/reflect/TaggedRcptr.hpp @@ -0,0 +1,88 @@ +/* file TaggedRcptr.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "reflect/TaggedPtr.hpp" +// causes #include cycle, reflect/Reflect.hpp includes this header +//#include "reflect/Reflect.hpp" +#include "refcnt/Refcounted.hpp" + +namespace xo { + namespace reflect { + /* Tagged reference-counted pointer. + * Like TaggedPtr, but also maintains reference count. + * + * note that refcounting behavior is lost if assigned to a TaggedPtr variable! + */ + class TaggedRcptr : public TaggedPtr { + public: + using Refcount = ref::Refcount; + + public: + TaggedRcptr(TypeDescr td, Refcount * x) : TaggedPtr(td, x) { + ref::intrusive_ptr_add_ref(x); + } + TaggedRcptr(TaggedRcptr const & x) : TaggedPtr(x) { + ref::intrusive_ptr_add_ref(x.rc_address()); + } + TaggedRcptr(TaggedRcptr && x) : TaggedPtr(std::move(x)) { + /* since we're moving from x, need to make sure x.dtor + * doesn't decrement refcount + */ + x.assign_address(nullptr); + } + ~TaggedRcptr() { + ref::intrusive_ptr_release(this->rc_address()); + } + + /* causes #include cycle, see [reflect/Reflect.hpp] */ +#ifdef NOT_IN_USE + /* require: T --isa--> ref::Refcount */ + template + static TaggedRcptr make(T * x) { return TaggedRcptr(Reflect::require(), x); } +#endif + + Refcount * rc_address() const { + return reinterpret_cast(this->address()); + } /*rc_address*/ + + TaggedRcptr & operator=(TaggedRcptr const & rhs) { + Refcount * x = rhs.rc_address(); + Refcount * old = this->rc_address(); + + TaggedPtr::operator=(rhs); + + if (x != old) { + intrusive_ptr_release(old); + intrusive_ptr_add_ref(x); + } + + return *this; + } /*operator=*/ + + TaggedRcptr & operator=(TaggedRcptr && rhs) { + /* swap pointers + type descriptions; + * then don't need to touch refcounts + */ + std::swap(this->td_, rhs.td_); + std::swap(this->address_, rhs.address_); + + return *this; + } /*operator=*/ + + void display(std::ostream & os) const; + std::string display_string() const; + }; /*TaggedRcptr*/ + + inline std::ostream & operator<<(std::ostream & os, TaggedRcptr const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TaggedRcptr.hpp */ diff --git a/include/reflect/TypeDescr.hpp b/include/reflect/TypeDescr.hpp new file mode 100644 index 00000000..08614071 --- /dev/null +++ b/include/reflect/TypeDescr.hpp @@ -0,0 +1,302 @@ +/* @file TypeDescr.hpp */ + +#pragma once + +//#include "reflect/atomic/AtomicTdx.hpp" +#include "reflect/TypeDescrExtra.hpp" +#include "cxxutil/demangle.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xo { + namespace reflect { + class TaggedPtr; /* see [reflect/TaggedPtr.hpp] */ + + /* A reflected type is a type for which we keep information around at runtime + * Assign reflected types unique (within an executable) ids, + * allocating consecutively, starting from 1. + * Reserve 0 as a sentinel + */ + class TypeId { + public: + /* allocate a new TypeId value. + * promise: + * - retval.id() > 0 + */ + static TypeId allocate() { return TypeId(s_next_id++); } + + std::uint32_t id() const { return id_; } + + private: + explicit TypeId(std::uint32_t id) : id_{id} {} + + private: + static std::uint32_t s_next_id; + + /* unique index# for this type. + * 0 reserved for sentinel + */ + std::uint32_t id_ = 0; + }; /*TypeId*/ + + inline std::ostream & + operator<<(std::ostream & os, TypeId x) { + os << x.id(); + return os; + } /*operator<<*/ + + /* runtime description of a struct/class instance variable */ + class StructMember; + + class TypeDescrBase; + + using TypeDescr = TypeDescrBase const *; + using TypeDescrW = TypeDescrBase *; + + /* convenience wrapper for a std::type_info pointer. + * works properly with pybind11, since python doens't encounter + * native type_info pointer, it won't try to delete it. + */ + class TypeInfoRef { + public: + explicit TypeInfoRef(std::type_info const * tinfo) : tinfo_{tinfo} {} + TypeInfoRef(TypeInfoRef const & x) = default; + + /* use: + * TypeInfoRef tinfo = TypeInfoRef::make(); + */ + template + TypeInfoRef make() { return TypeInfoRef(&typeid(T)); } + + std::size_t hash_code() const { return this->tinfo_->hash_code(); } + char const * impl_name() const { return this->tinfo_->name(); } + + static bool is_equal(TypeInfoRef x, TypeInfoRef y) noexcept { + if (x.hash_code() != y.hash_code()) + return false; + + return ::strcmp(x.impl_name(), y.impl_name()) == 0; + } /*is_equal*/ + + private: + /* native type_info object for encapsulated type */ + std::type_info const * tinfo_ = nullptr; + }; /*TypeInfoRef*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +namespace std { + template <> struct hash { + std::size_t operator()(xo::reflect::TypeInfoRef x) const noexcept { return x.hash_code(); } + }; +} /*namespace std*/ + +namespace xo { + namespace reflect { + inline bool operator==(TypeInfoRef x, TypeInfoRef y) { return TypeInfoRef::is_equal(x, y); } + inline bool operator!=(TypeInfoRef x, TypeInfoRef y) { return !TypeInfoRef::is_equal(x, y); } + +#ifdef NOT_IN_USE + namespace detail { + class HashTypeInfoRef { + public: + std::size_t operator()(TypeInfoRef x) const noexcept { return x.hash_code(); } + }; /*HashTypeInfoRef*/ + + class EqualTypeInfoRef { + public: + bool operator()(TypeInfoRef x, TypeInfoRef y) const noexcept { return TypeInfoRef::is_equal(x, y); } + }; /*EqualTypeInfoRef*/ + } /*namespace detail*/ +#endif + + class TypeDescrExtra; + + /* run-time description for a native c++ type */ + class TypeDescrBase { + public: + /* type-description objects for a type T is unique, + * --> can always use its address + */ + TypeDescrBase(TypeDescrBase const & x) = delete; + + /* test whether a type has been reflected. + * introducing this for unit testing + */ + static bool is_reflected(std::type_info const * tinfo) { + return (s_type_table_map.find(TypeInfoRef(tinfo)) + != s_type_table_map.end()); + } /*is_reflected*/ + + /* NOTE: + * implementation here will be defeated if std::type_info + * objects violate ODR. This occurs with clang + 2-level namespaces, + * so important to linke with --flat_namespace defined. + * See FAQ + * [Build Issues|Q2 - dynamic_cast> fails] + */ + static TypeDescrW require(std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra); + + /* print table of reflected types to os */ + static void print_reflected_types(std::ostream & os); + + TypeId id() const { return id_; } + std::type_info const * typeinfo() const { return typeinfo_; } + std::string_view const & canonical_name() const { return canonical_name_; } + std::string_view const & short_name() const { return short_name_; } + bool complete_flag() const { return complete_flag_; } + TypeDescrExtra * tdextra() const { return tdextra_.get(); } + Metatype metatype() const { return tdextra_->metatype(); } + + /* true iff the type represented by *this is the same as the type T. + * + * Warning: comparing typeinfo address can give false negatives. + * suspect this is caused by problems coalescing linker symbols + * in the clang toolchain. + */ + template + bool is_native() const { + return ((this->typeinfo() == &typeid(T)) + || (this->typeinfo()->hash_code() == typeid(T).hash_code()) + || (this->typeinfo()->name() == typeid(T).name())); + } /*is_native*/ + + /* safe downcast -- like dynamic_cast<>, but does not require a source type */ + template + T * recover_native(void * address) const { + if (this->is_native()) { + return reinterpret_cast(address); + } else { + return nullptr; + } + } /*recover_native*/ + + bool is_vector() const { return this->tdextra_->is_vector(); } + bool is_struct() const { return this->tdextra_->is_struct(); } + + /* given a T-instance object, return tagged pointer with T replaced + * by the most-derived-subtype of T to which *object belongs. + * This works only for descendants of reflect::SelfTagging + */ + TaggedPtr most_derived_self_tp(void * object) const; + + /* if generalized vector (std::vector, std::array, ..): + * .n_child() reports #of elements + * if struct/class: + * .n_child() reports #of instance variables (that have been reflected) + */ + uint32_t n_child(void * object) const { return this->tdextra_->n_child(object); } + TaggedPtr child_tp(uint32_t i, void * object) const; + + /* require: + * - .is_struct() = true + * - i in [0 .. .n_child() - 1] + */ + std::string const & struct_member_name(uint32_t i) const { + return this->tdextra_->struct_member_name(i); + } + /* fetch runtime description for i'th reflected instance variable. + * + * require: + * - .is_struct() = true + * - i in [0 .. .n_child() - 1] + */ + StructMember const & struct_member(uint32_t i) const { + StructMember const * sm = this->tdextra_->struct_member(i); + + assert(sm); + return *sm; + } /*struct_member*/ + + void display(std::ostream & os) const; + std::string display_string() const; + + /* mark this TypeDescr complete; + * returns the value of .complete_flag from _before_ + * this call + */ + bool mark_complete(); + + /* call this once to attach extended type information to a type-description + * (e.g. description of struct members for a record type) + */ + void assign_tdextra(std::unique_ptr tdx); + + private: + TypeDescrBase(TypeId id, + std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra); + + private: + /* invariant: + * - for all TypeDescrImpl instances x: + * - s_type_table_v[x->id()] = x + * - s_type_table_map[TypeInfoRef(x->typeinfo())] = x + */ + + /* hashmap of all TypeDescr instances, indexed by . singleton */ + static std::unordered_map> s_type_table_map; + /* hashmap of (presumed) duplicate TypeInfoRef values. + * This happens with clang sometimes when the same type is referenced + * from multiple modules (i.e. shared libs). + */ + static std::unordered_map s_coalesced_type_table_map; + + /* vector of all TypeDescr instances. singleton. */ + static std::vector s_type_table_v; + + private: + /* unique id# for this type */ + TypeId id_; + /* typeinfo for type T */ + std::type_info const * typeinfo_ = nullptr; + /* canonical name for this type (see demangle.hpp for type_name()) + * e.g. + * xo::option::Px2 + */ + std::string_view canonical_name_; + /* suffix of .canonical_name, just after last ':' + * e.g. + * Px2 + */ + std::string_view short_name_; + /* set to true once final value for .tdextra is established + * intially all TypeDescr objects will use AtomicTdx for .tdextra + * Reflect::require() upgrades .tdextra for particular types. + * When that procedure makes a decision for a type T, + * .complete_flag will be set to true for the corresponding TypeDescrBase instance + */ + bool complete_flag_ = false; + /* additional type information that either: + * (a) isn't universal across all types, + * e.g. dereferencing instance of a pointer type + * (b) can't be captured with template-fu, + * e.g. struct member names + * + * generally .tdextra will be populated some time after TypeDescrBase's ctor exits. + * This is necessary because of (b) above, also because of possibility of recursive + * types. + */ + std::unique_ptr tdextra_; + }; /*TypeDescrBase*/ + + inline std::ostream & + operator<<(std::ostream & os, TypeDescrBase const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TypeDescr.hpp */ diff --git a/include/reflect/TypeDescrExtra.hpp b/include/reflect/TypeDescrExtra.hpp new file mode 100644 index 00000000..2d2be7fa --- /dev/null +++ b/include/reflect/TypeDescrExtra.hpp @@ -0,0 +1,65 @@ +/* @file TypeDescrExtra.hpp */ + +#pragma once + +#include "reflect/Metatype.hpp" +#include +/* note: this file #include'd into TypeDescr.hpp */ +#include + +namespace xo { + namespace reflect { + /* forward-declaring here. see [reflect/struct/StructMember.hpp] */ + class StructMember; + class TypeDescrBase; + class TaggedPtr; + + /* information associated with a c++ type. + * distinct from TypeDescrImpl: + * 1. want to use reflection to support for runtime polymorphism over similar but + * not directly-related types: for example + * std::vector + * and + * std::list + * are both ordered collections + * 2. some information can't be universally established via template-fu, + * for example struct member names + * 3. descriptions for recursive types require 2-stage construction + * + * A TypeDescrImpl instance will contain a pointer to a suitable + * TypeDescrExtra instance. + * + * The single TypeDescrImpl instance for some type T can be established + * automatically, see Reflect::require(). + * + * A specific TypeDescrExtra instance may be attached in a non-automated way + * later + */ + class TypeDescrExtra { + public: + using uint32_t = std::uint32_t; + + public: + virtual ~TypeDescrExtra() = default; + + bool is_vector() const { return this->metatype() == Metatype::mt_vector; } + bool is_struct() const { return this->metatype() == Metatype::mt_struct; } + + virtual Metatype metatype() const = 0; + /* given a T-instance, report most-derived subtype of T to which *object belongs. + * this works only for types that are derived from reflect::SelfTagging. + */ + virtual TaggedPtr most_derived_self_tp(TypeDescrBase const * object_td, void * object) const; + virtual uint32_t n_child(void * object) const = 0; + virtual TaggedPtr child_tp(uint32_t i, void * object) const = 0; + /* require: + * .is_struct() + */ + virtual std::string const & struct_member_name(uint32_t i) const = 0; + /* nullptr unless *this represents a struct/class type */ + virtual StructMember const * struct_member(uint32_t i) const; + }; /*TypeDescrExtra*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TypeDescrExtra.hpp */ diff --git a/include/reflect/TypeDrivenMap.hpp b/include/reflect/TypeDrivenMap.hpp new file mode 100644 index 00000000..c38c8739 --- /dev/null +++ b/include/reflect/TypeDrivenMap.hpp @@ -0,0 +1,47 @@ +/* @file TypeDrivenMap.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "reflect/TypeDescr.hpp" +#include + +namespace xo { + namespace reflect { + /* represents a map :: TypeId -> Value */ + template + class TypeDrivenMap { + public: + Value const * lookup(TypeId id) const { return this->lookup_slot(id); } + + Value * require(TypeId id) { return this->require_slot(id); } + Value * require(TypeDescr td) { return this->require_slot(td->id()); } + + private: + Value const * lookup_slot(TypeId id) const { + if (this->contents_v_.size() <= id.id()) + return nullptr; + + return &(this->contents_v_[id.id()]); + } /*lookup_slot*/ + + Value * require_slot(TypeId id) { + if (this->contents_v_.size() <= id.id()) + this->contents_v_.resize(id.id() + 1); + + return &(this->contents_v_[id.id()]); + } /*require_slot*/ + + private: + /* since TypeId/s are unique, compact sequence numbers, + * can efficiently store mapping to Values using a vector indexed by TypeId + */ + std::vector contents_v_; + }; /*TypeDrivenMap*/ + } /*namespace reflect*/ +} /*namespace xo*/ + + +/* end TypeDrivenMap.hpp */ diff --git a/include/reflect/atomic/AtomicTdx.hpp b/include/reflect/atomic/AtomicTdx.hpp new file mode 100644 index 00000000..7b2e042d --- /dev/null +++ b/include/reflect/atomic/AtomicTdx.hpp @@ -0,0 +1,37 @@ +/* @file AtomicTdx.hpp */ + +#pragma once + +#include "reflect/TypeDescrExtra.hpp" +//#include "reflect/TaggedPtr.hpp" +#include + +namespace xo { + namespace reflect { + class TaggedPtr; + + /* Extra type-associated information for an atomic type. + * We use this as degenerate catch-all case for types that aren't known + * to have additional structure (std::vector, std::map, int*, etc.) + */ + class AtomicTdx : public TypeDescrExtra { + public: + virtual ~AtomicTdx() = default; + + static std::unique_ptr make(); + + // ----- Inherited from TypeDescrExtra ----- + + virtual Metatype metatype() const override { return Metatype::mt_atomic; } + virtual uint32_t n_child(void * /*object*/) const override { return 0; } + virtual TaggedPtr child_tp(uint32_t /*i*/, void * /*object*/) const override; + virtual std::string const & struct_member_name(uint32_t i) const override; + //virtual StructMember const * struct_member(uint32_t /*i*/) const override { return nullptr; } + + private: + AtomicTdx() = default; + }; /*TypeDescrExtra*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end AtomicTdx.hpp */ diff --git a/include/reflect/init_reflect.hpp b/include/reflect/init_reflect.hpp new file mode 100644 index 00000000..00506a25 --- /dev/null +++ b/include/reflect/init_reflect.hpp @@ -0,0 +1,22 @@ +/* file init_reflect.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "subsys/Subsystem.hpp" + +namespace xo { + /* tag to represent the reflect/ subsystem within ordered initialization */ + enum S_reflect_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + + +/* end init_reflect.hpp */ diff --git a/include/reflect/pointer/PointerTdx.hpp b/include/reflect/pointer/PointerTdx.hpp new file mode 100644 index 00000000..d2d3b868 --- /dev/null +++ b/include/reflect/pointer/PointerTdx.hpp @@ -0,0 +1,76 @@ +/* file PointerTdx.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "reflect/TypeDescrExtra.hpp" +#include "reflect/EstablishTypeDescr.hpp" +#include "indentlog/scope.hpp" + +namespace xo { + namespace reflect { + /* Extra type-associated information for a pointer-like type + * + * Treat a pointer as a container that has 0 or 1 children; + * - 0 children if null + * - 1 child otherwise + */ + class PointerTdx : public TypeDescrExtra { + public: + // ----- Inherited from TypeDescrExtra ----- + + virtual Metatype metatype() const override { return Metatype::mt_pointer; } + virtual uint32_t n_child(void * object) const override = 0; + virtual TaggedPtr child_tp(uint32_t i, void * object) const override = 0; + /* (forbidden) */ + virtual std::string const & struct_member_name(uint32_t i) const override; + }; /*PointerTdx*/ + + // ----- RefPointerTdx ----- + + /* xo::ref::intrusive_ptr for some T */ + template + class RefPointerTdx : public PointerTdx { + public: + using target_t = Pointer; + + static std::unique_ptr make() { + return std::unique_ptr(new RefPointerTdx()); + } /*make*/ + + virtual uint32_t n_child(void * object) const override { + /* e.g: + * target_t = ref::rp + */ + target_t * ptr = reinterpret_cast(object); + + if (*ptr) + return 1; + else + return 0; + } /*n_child*/ + + virtual TaggedPtr child_tp(uint32_t i, void * object) const override { + using xo::tostr; + using xo::xtag; + + target_t * ptr = reinterpret_cast(object); + + if (i > 0) { + throw std::runtime_error(tostr("RefPointerTdx::child_tp" + ": attempt to fetch child #i from a ref::rp", + xtag("T", type_name()), + xtag("i", i), + xtag("n", this->n_child(object)))); + } + + return establish_most_derived_tp(ptr->get()); + } /*child_tp*/ + }; /*RefPointerTdx*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end PointerTdx.hpp */ diff --git a/include/reflect/struct/StructMember.hpp b/include/reflect/struct/StructMember.hpp new file mode 100644 index 00000000..eaddf3b3 --- /dev/null +++ b/include/reflect/struct/StructMember.hpp @@ -0,0 +1,236 @@ +/* @file StructMember.hpp */ + +#pragma once + +#include "reflect/TypeDescr.hpp" +#include "reflect/EstablishTypeDescr.hpp" +//#include "reflect/Reflect.hpp" +#include "reflect/TaggedPtr.hpp" +#include +#include + +namespace xo { +namespace reflect { + class AbstractStructMemberAccessor { + public: + virtual ~AbstractStructMemberAccessor() = default; + + /* get tagged pointer referring to this member of the object at *struct_addr */ + TaggedPtr member_tp(void * struct_addr) const; + + /* get type-description object for struct + * containing this member. useful for consistency checking. + */ + virtual TypeDescr struct_td() const = 0; + + /* get type-description object for this member + * e.g. if this member represents Foo::bar_ in + * struct Foo { int bar_; }; + * then + * .member_td() => Reflect::require(); + */ + virtual TypeDescr member_td() const = 0; + + /* get address of a particular member, given parent address */ + virtual void * address(void * struct_addr) const = 0; + + virtual std::unique_ptr clone() const = 0; + }; /*AbstractStructMemberAccessor*/ + + /* GeneralStructMemberAccessor + * + * Use this to handle access to possibly-inherited struct members: + * + * struct Foo { int x_; } + * struct Bar { char * y_; } + * struct Quux : public Foo, public Bar { bool z_; } + * + * want to be able to access Bar::y from a Quux instance. + * in example, would use GenericStructMemberAccessor<> + * with: + * StructT = Quux, + * OwnerT = Bar, + * MemberT = char* + * + * Require: + * StructT* is assignable to OwnerT* (because StructT --isa--> OwnerT) + */ + template + class GeneralStructMemberAccessor : public AbstractStructMemberAccessor { + public: + /* pointer to a OwnerT member of type MemberT */ + using Memptr = MemberT OwnerT::*; + + public: + GeneralStructMemberAccessor(Memptr memptr) : member_td_{EstablishTypeDescr::establish()}, + memptr_{memptr} {} + GeneralStructMemberAccessor(GeneralStructMemberAccessor const & x) = default; + virtual ~GeneralStructMemberAccessor() = default; + + static std::unique_ptr make(Memptr memptr) { + return std::unique_ptr(new GeneralStructMemberAccessor(memptr)); } + + /* get member address given address of parent struct + * (i.e. from Struct*, not from OwnerT*) + */ + MemberT * address_impl(StructT * self_addr) const { + OwnerT * owner_addr = self_addr; + + return &(owner_addr->*memptr_); + } /*address_impl*/ + + // ----- Inherited from AbstractStructMemberAccessor ----- + +#ifdef OBSOLETE + virtual TaggedPtr member_tp(void * struct_addr) const override { + /* FIXME: this reports declared type of member, instead of + * (possibly narrower) actual type of member + */ + + return this->member_td_->most_derived_self_tp(this->address(struct_addr)); + //return TaggedPtr(this->member_td_, this->address(struct_addr)); + } /*member_tp*/ +#endif + + virtual TypeDescr struct_td() const override { return EstablishTypeDescr::establish(); } + + virtual TypeDescr member_td() const override { return this->member_td_; } + + virtual void * address(void * struct_addr) const override { + return this->address_impl(reinterpret_cast(struct_addr)); + } /*address*/ + + virtual std::unique_ptr clone() const override { + return std::unique_ptr + (new GeneralStructMemberAccessor(*this)); + } /*clone*/ + + private: + /* type description for MemberT; .memptr is pointer-to-member-of-OwnerT, + * where that member has type MemberT + */ + TypeDescr member_td_ = nullptr; + /* pointer to member of OwnerT */ + Memptr memptr_ = nullptr; + }; /*GeneralStructMemberAccessor*/ + + /* struct-member accessor via delegation, + * to accessor of a parent (or some other ancestor) class. + * + * struct Foo { int x_; } + * struct Bar { char * y_; } + * + * auto bar_x_access = GeneralStructMemberAccessor::make(&Foo::x_); + * + * or equivalently: + * auto foo_x_access = GeneralStructMemberAccessor::make(&Foo::x_); + * auto bar_x_access = AncestorStructMemberAccessor::adopt(foo_x_access); + * + * can use the 2nd form to adopt accessors from an already-reflected ancestor class + * + * Require: + * - StructT -isa-> AncestorT + */ + template + class AncestorStructMemberAccessor : public AbstractStructMemberAccessor { + public: + AncestorStructMemberAccessor(std::unique_ptr ancestor_accessor) + : ancestor_accessor_{std::move(ancestor_accessor)} {} + AncestorStructMemberAccessor(AncestorStructMemberAccessor const & x) = default; + virtual ~AncestorStructMemberAccessor() = default; + + static std::unique_ptr + adopt(std::unique_ptr ancestor_accessor) { + return std::unique_ptr + (new AncestorStructMemberAccessor(std::move(ancestor_accessor))); + } /*adopt*/ + + void * address_impl(StructT * self_addr) const { + /* to use access-via-ancestor, need to convert to ancestor pointer */ + AncestorT * ancestor_addr = self_addr; + + return this->ancestor_accessor_->address(ancestor_addr); + } /*address_impl*/ + + // ----- inherited from AbstractStructMemberAccessor ----- + +#ifdef OBSOLETE + virtual TaggedPtr member_tp(void * struct_addr) const override { + AncestorT * ancestor_addr = reinterpret_cast(struct_addr); + + return this->ancestor_accessor_->member_tp(ancestor_addr); + } /*member_tp*/ +#endif + + virtual TypeDescr struct_td() const override { return EstablishTypeDescr::establish(); } + virtual TypeDescr member_td() const override { return this->ancestor_accessor_->member_td(); } + + virtual void * address(void * struct_addr) const override { + return this->address_impl(reinterpret_cast(struct_addr)); + } + + virtual std::unique_ptr clone() const override { + return std::unique_ptr + (new AncestorStructMemberAccessor(std::move(this->ancestor_accessor_->clone()))); + } /*clone*/ + + private: + /* .ancestor_accessor fetches some particular member of AncestorT */ + std::unique_ptr ancestor_accessor_; + }; /*AncestorStructMemberAccessor*/ + + /* describes a member of a struct/class + * see [reflect/StructReflector.hpp] + */ + class StructMember { + public: + StructMember() = default; + StructMember(std::string const & name, + std::unique_ptr accessor) + : member_name_{name}, accessor_{std::move(accessor)} {} + StructMember(StructMember && x) + : member_name_{std::move(x.member_name_)}, + accessor_{std::move(x.accessor_)} {} + + static StructMember null(); + + std::string const & member_name() const { return member_name_; } + + TaggedPtr get_member_tp(void * struct_addr) const { return this->accessor_->member_tp(struct_addr); } + TypeDescr get_struct_td() const { return this->accessor_->struct_td(); } + TypeDescr get_member_td() const { return this->accessor_->member_td(); } + //void * get_member_addr(void * struct_addr) const { return this->accessor_->address(struct_addr); } + + /* make copy that accesses this member, but starting + * from pointer to some derived class DescendantT, + * instead of from container type StructT known to (but not exposed by) *this + */ + template + StructMember for_descendant() const { + assert(EstablishTypeDescr::establish() == this->get_struct_td()); + + return StructMember(this->member_name(), + std::move(AncestorStructMemberAccessor::adopt + (std::move(this->accessor_->clone())))); + } /*for_descendant*/ + + StructMember & operator=(StructMember && x) { + member_name_ = std::move(x.member_name_); + accessor_ = std::move(x.accessor_); + return *this; + } + + private: + /* member name, e.g. foo if + * struct StructT { MemberT foo; } + */ + std::string member_name_; + /* T recd; + * this->accessor_->address_impl(&recd) ==> &(recd.member) + */ + std::unique_ptr accessor_; + }; /*StructMember*/ +} /*namespace reflect*/ +} /*namespace xo*/ + +/* end StructMember.hpp */ diff --git a/include/reflect/struct/StructTdx.hpp b/include/reflect/struct/StructTdx.hpp new file mode 100644 index 00000000..5e170ef7 --- /dev/null +++ b/include/reflect/struct/StructTdx.hpp @@ -0,0 +1,94 @@ +/* @file StructTdx.hpp */ + +#pragma once + +#include "reflect/TypeDescrExtra.hpp" +#include "reflect/TaggedPtr.hpp" +#include "reflect/struct/StructMember.hpp" +#include +#include +#include + +namespace xo { + namespace reflect { + /* Extra type-associated information for a struct/class. + * We use this to preserve information about memory layout + * at runtime + */ + class StructTdx : public TypeDescrExtra { + public: + /* named ctor idiom. create new instance for struct with given member list + * + * to_self_tp. use this function to support .most_derived_self_tp() + */ + static std::unique_ptr make(std::vector member_v, + bool have_to_self_tp, + std::function to_self_tp); + + /* specialization for std::pair + * coordinates with [reflect/Reflect.hpp] + */ + template + static std::unique_ptr pair() { + using struct_t = std::pair; + + std::vector mv; + { + auto lhs_access + (GeneralStructMemberAccessor::make + (&struct_t::first)); + + mv.push_back(StructMember("first", std::move(lhs_access))); + } + { + auto rhs_access + (GeneralStructMemberAccessor::make + (&struct_t::second)); + + mv.push_back(StructMember("second", std::move(rhs_access))); + } + + std::function null_to_self_tp; + + return make(std::move(mv), + false /*!have_to_self_tp*/, + null_to_self_tp); + } /*pair*/ + + // ----- Inherited from TypeDescrExtra ----- + + virtual Metatype metatype() const override { return Metatype::mt_struct; } + virtual TaggedPtr most_derived_self_tp(TypeDescrBase const * object_td, + void * object) const override { + if (this->have_to_self_tp_) { + return this->to_self_tp_(object); + } else { + return TypeDescrExtra::most_derived_self_tp(object_td, object); + } + } + virtual uint32_t n_child(void * /*object*/) const override { return this->member_v_.size(); } + virtual TaggedPtr child_tp(uint32_t i, void * object) const override; + virtual std::string const & struct_member_name(uint32_t i) const override; + virtual StructMember const * struct_member(uint32_t i) const override; + + private: + StructTdx(std::vector member_v, + bool have_to_self_tp, + std::function to_self_tp) + : member_v_{std::move(member_v)}, + have_to_self_tp_{have_to_self_tp}, + to_self_tp_{std::move(to_self_tp)} {} + + private: + /* per-instance-variable reflection details */ + std::vector member_v_; + /* true if .to_self_tp() is defined */ + bool have_to_self_tp_ = false; + /* get TaggedPtr for most-derived subtype of supplied T-instance */ + std::function to_self_tp_; + }; /*StructTdx*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end StructTdx.hpp */ diff --git a/include/reflect/vector/VectorTdx.hpp b/include/reflect/vector/VectorTdx.hpp new file mode 100644 index 00000000..4f3309c2 --- /dev/null +++ b/include/reflect/vector/VectorTdx.hpp @@ -0,0 +1,100 @@ +/* file VectorTdx.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "reflect/TypeDescrExtra.hpp" +//#include "reflect/TaggedPtr.hpp" +#include "reflect/EstablishTypeDescr.hpp" +//#include "reflect/TaggedPtr.hpp" +//#include +//#include + +namespace xo { + namespace reflect { + /* Extra type-associated information for a vector/array. + */ + class VectorTdx : public TypeDescrExtra { + public: + /* named ctor idiom. create new instance for a vector type */ + //static std::unique_ptr make(); + + // ----- Inherited from TypeDescrExtra ----- + + virtual Metatype metatype() const override { return Metatype::mt_vector; } + virtual uint32_t n_child(void * object) const override = 0; + virtual TaggedPtr child_tp(uint32_t i, void * object) const override = 0; + /* (forbidden) */ + virtual std::string const & struct_member_name(uint32_t i) const override; + }; /*VectorTdx*/ + + // ----- StlVectorTdx ----- + + /* require: + * - VectorT.size() + * - VectorT[int] :: lvalue + */ + template + class StlVectorTdx : public VectorTdx { + public: + using target_t = VectorT; + + static std::unique_ptr make() { + return std::unique_ptr(new StlVectorTdx()); + } /*make*/ + + virtual uint32_t n_child(void * object) const override { + target_t * vec = reinterpret_cast(object); + + return vec->size(); + } /*n_child*/ + + virtual TaggedPtr child_tp(uint32_t i, void * object) const override { + target_t * vec = reinterpret_cast(object); + + return establish_most_derived_tp(&((*vec)[i])); + } /*child_tp*/ + }; /*StlVectorTdx*/ + + // ----- std::array ----- + + /* coordinates with EstablishTdx>::make(), + * see [reflect/Reflect.hpp] + */ + + template + using StdArrayTdx = StlVectorTdx>; + + // ----- std::vector ----- + + /* coordinates with EstablishTdx>::make() + * see [reflect/Reflect.hpp] + */ + template + class StdVectorTdx : public VectorTdx { + public: + using target_t = std::vector; + + static std::unique_ptr make() { + return std::unique_ptr(new StdVectorTdx()); + } /*make*/ + + virtual uint32_t n_child(void * object) const override { + target_t * vec = reinterpret_cast(object); + + return vec->size(); + } /*n_child*/ + + virtual TaggedPtr child_tp(uint32_t i, void * object) const override { + target_t * vec = reinterpret_cast(object); + + return establish_most_derived_tp(&((*vec)[i])); + } + }; /*StdVectorTdx*/ + } /*namespace reflect*/ + +} /*namespace xo*/ + +/* end VectorTdx.hpp */ diff --git a/repo/README.md b/repo/README.md new file mode 100644 index 00000000..3acdcde4 --- /dev/null +++ b/repo/README.md @@ -0,0 +1,5 @@ + +``` +cd reflect/repo +git submodule add git@github.com/someusername/someproject.git +``` diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt new file mode 100644 index 00000000..e1c1f65f --- /dev/null +++ b/src/reflect/CMakeLists.txt @@ -0,0 +1,52 @@ +# reflect/CMakeLists.txt + +set(SELF_LIBRARY_NAME reflect) +set(SELF_SOURCE_FILES TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp atomic/AtomicTdx.cpp pointer/PointerTdx.cpp vector/VectorTdx.cpp struct/StructTdx.cpp struct/StructMember.cpp init_reflect.cpp) + +# build shared library 'reflect' +add_library(${SELF_LIBRARY_NAME} SHARED ${SELF_SOURCE_FILES}) + +set_target_properties(${SELF_LIBRARY_NAME} + PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION 1) + +# ---------------------------------------------------------------- +# all the errors+warnings! +# +#target_compile_options(${SELF_LIBRARY_NAME} PRIVATE -Werror -Wall -Wextra) +xo_compile_options(${SELF_LIBRARY_NAME}) +xo_include_options(${SELF_LIBRARY_NAME}) +xo_install_library(${SELF_LIBRARY_NAME}) + +# ---------------------------------------------------------------- +# dependencies: logutil, ... + +#xo_refcnt_dependency(${SELF_LIBRARY_NAME}) +#xo_indentlog_dependency(${SELF_LIBRARY_NAME}) + +add_dependencies(${SELF_LIBRARY_NAME} refcnt) +target_include_directories( + ${SELF_LIBRARY_NAME} PUBLIC + $ + $ +) +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) + +add_dependencies(${SELF_LIBRARY_NAME} indentlog) +# note: can't use find_package() here, +# because find_package() needs to run successfully before +# dependency gets installed. +target_include_directories( + ${SELF_LIBRARY_NAME} PUBLIC + $ + $ +) +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC indentlog) + +# ---------------------------------------------------------------- +# 3rd party dependency: boost: + +#xo_boost_dependency(${SELF_LIBRARY_NAME}) + +# end CMakeLists.txt diff --git a/src/reflect/TaggedRcptr.cpp b/src/reflect/TaggedRcptr.cpp new file mode 100644 index 00000000..cc461e0c --- /dev/null +++ b/src/reflect/TaggedRcptr.cpp @@ -0,0 +1,30 @@ +/* file TaggedRcptr.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "TaggedRcptr.hpp" +#include "indentlog/print/tag.hpp" + +namespace xo { + using xo::xtag; + using xo::tostr; + + namespace reflect { + void + TaggedRcptr::display(std::ostream & os) const + { + os << "td()->canonical_name()) + << xtag("addr", this->rc_address()) + << ">"; + } /*display*/ + + std::string + TaggedRcptr::display_string() const { + return tostr(*this); + } /*display_string*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TaggedRcptr.cpp */ diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp new file mode 100644 index 00000000..564fec14 --- /dev/null +++ b/src/reflect/TypeDescr.cpp @@ -0,0 +1,194 @@ +/* @file TypeDescr.cpp */ + +#include "TypeDescr.hpp" +#include "TaggedPtr.hpp" +#include "TypeDescrExtra.hpp" +#include "atomic/AtomicTdx.hpp" +#include "indentlog/scope.hpp" + +namespace xo { + using xo::scope; + using xo::xtag; + using xo::tostr; + + namespace reflect { + uint32_t + TypeId::s_next_id = 1; + + std::unordered_map> + TypeDescrBase::s_type_table_map; + + std::unordered_map + TypeDescrBase::s_coalesced_type_table_map; + + std::vector + TypeDescrBase::s_type_table_v; + + TypeDescrW + TypeDescrBase::require(std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra) + { + /* 1. lookup by tinfo hash_code in s_type_table_map */ + { + auto ix = s_type_table_map.find(TypeInfoRef(tinfo)); + + if ((ix != s_type_table_map.end()) && ix->second) + return ix->second.get(); + } + + /* 2. lookup by tinfo hash_code in s_coalesced_type_table_map */ + { + auto ix = s_coalesced_type_table_map.find(TypeInfoRef(tinfo)); + + if ((ix != s_coalesced_type_table_map.end()) && ix->second) + return ix->second; + } + + /* 3. O(n) lookup by canonical_name, before we create a new slot. + * + * Have to accept that on clang type_info objects aren't always unique (!$@#!!) + * + * TODO: lookup table keyed by canonical_name + */ + for (TypeDescrBase * x : s_type_table_v) { + if (x && (x->canonical_name() == canonical_name)) { + /* 1. assume *x represents the type associated with tinfo. + * 2. *do* store tinfo in s_coalesced_type_table_map[], + * for faster lookup next time + */ + s_coalesced_type_table_map[TypeInfoRef(tinfo)] = x; + + return x; + } + } + + TypeId id = TypeId::allocate(); + + std::unique_ptr & slot = s_type_table_map[TypeInfoRef(tinfo)]; + + slot.reset(new TypeDescrBase(id, + tinfo, + canonical_name, + std::move(tdextra))); + + if (s_type_table_v.size() <= id.id()) + s_type_table_v.resize(id.id() + 1); + + s_type_table_v[id.id()] = slot.get(); + + return slot.get(); + } /*require*/ + + void + TypeDescrBase::print_reflected_types(std::ostream & os) + { + os << "display(os); + } + } + + os << ">\n"; + } /*print_reflected_types*/ + + namespace { + /* readability hack: + * foo::bar::Quux ==> Quux + * but lookout for template names: + * std::pair ==> pair + */ + std::string_view + unqualified_name(std::string_view const & canonical_name) + { + size_t m = canonical_name.find_first_of('<'); + + /* skip ':', but only in range [0..m) */ + size_t p = canonical_name.find_last_of(':', m); + + if (p == std::string_view::npos) { + return canonical_name; + } else { + if ((canonical_name.substr(0, 9) == "std::pair") + || (canonical_name.substr(0, 13) == "std::_1::pair")) + { + return std::string_view("pair"); + } else { + return std::string_view(canonical_name.substr(p+1)); + } + } + } /*unqualified_name*/ + } /*namespace*/ + + TypeDescrBase::TypeDescrBase(TypeId id, + std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra) + : id_{std::move(id)}, + typeinfo_{tinfo}, + canonical_name_{std::move(canonical_name)}, + short_name_{unqualified_name(canonical_name_)}, + tdextra_{std::move(tdextra)} + { + } + + TaggedPtr + TypeDescrBase::most_derived_self_tp(void * object) const + { + return this->tdextra_->most_derived_self_tp(this, object); + } /*most_derived_self_tp*/ + + TaggedPtr + TypeDescrBase::child_tp(uint32_t i, void * object) const + { + return this->tdextra_->child_tp(i, object); + } /*child_tp*/ + + void + TypeDescrBase::display(std::ostream & os) const + { + os << "metatype()) + << ">"; + } /*display*/ + + std::string + TypeDescrBase::display_string() const + { + return tostr(*this); + } /*display_string*/ + + bool + TypeDescrBase::mark_complete() + { + bool retval = this->complete_flag_; + + this->complete_flag_ = true; + + return retval; + } /*mark_complete*/ + + void + TypeDescrBase::assign_tdextra(std::unique_ptr tdx) + { + scope log(XO_ENTER0(verbose), + xtag("canonical_name", this->canonical_name()), + xtag("tdextra.old", this->tdextra_.get()), + xtag("metatype.old", (this->tdextra_ + ? this->tdextra_->metatype() + : Metatype::mt_invalid)), + xtag("metatype.new", tdx->metatype())); + + this->complete_flag_ = true; + this->tdextra_ = std::move(tdx); + } /*assign_tdextra*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TypeDescr.cpp */ diff --git a/src/reflect/TypeDescrExtra.cpp b/src/reflect/TypeDescrExtra.cpp new file mode 100644 index 00000000..e53a216b --- /dev/null +++ b/src/reflect/TypeDescrExtra.cpp @@ -0,0 +1,37 @@ +/* file TypeDescrExtra.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "TypeDescrExtra.hpp" +#include "TypeDescr.hpp" +#include "TaggedPtr.hpp" +#include + +namespace xo { + namespace reflect { + TaggedPtr + TypeDescrExtra::most_derived_self_tp(TypeDescrBase const * object_td, + void * object) const + { + return TaggedPtr(object_td, object); + } /*most_derived_self_tp*/ + + std::string const & + TypeDescrExtra::struct_member_name(uint32_t /*i*/) const { + assert(false); + + static std::string s_null; + return s_null; + } /*struct_member_name*/ + + StructMember const * + TypeDescrExtra::struct_member(uint32_t /*i*/) const { + assert(false); + + return nullptr; + } /*struct_member*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end TypeDescrExtra.cpp */ diff --git a/src/reflect/atomic/AtomicTdx.cpp b/src/reflect/atomic/AtomicTdx.cpp new file mode 100644 index 00000000..42ca9e2f --- /dev/null +++ b/src/reflect/atomic/AtomicTdx.cpp @@ -0,0 +1,24 @@ +/* @file AtomicTdx.cpp */ + +#include "atomic/AtomicTdx.hpp" +#include "TaggedPtr.hpp" + +namespace xo { + namespace reflect { + std::unique_ptr AtomicTdx::make() { + return std::unique_ptr(new AtomicTdx()); + } /*make*/ + + TaggedPtr + AtomicTdx::child_tp(uint32_t /*i*/, void * /*object*/) const { + return TaggedPtr::universal_null(); + } /*child_tp*/ + + std::string const & + AtomicTdx::struct_member_name(uint32_t i) const { + return TypeDescrExtra::struct_member_name(i); + } /*struct_member_name*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end AtomicTdx.cpp */ diff --git a/src/reflect/init_reflect.cpp b/src/reflect/init_reflect.cpp new file mode 100644 index 00000000..20dd1441 --- /dev/null +++ b/src/reflect/init_reflect.cpp @@ -0,0 +1,23 @@ +/* file init_reflect.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "init_reflect.hpp" +#include "subsys/Subsystem.hpp" + +namespace xo { + void + InitSubsys::init() + { + /* placeholder -- expecting there to be non-trivial content soon */ + } /*init*/ + + InitEvidence + InitSubsys::require() + { + return Subsystem::provide("reflect", &init); + } /*require*/ +} /*namespace xo*/ + +/* end init_reflect.cpp */ diff --git a/src/reflect/pointer/PointerTdx.cpp b/src/reflect/pointer/PointerTdx.cpp new file mode 100644 index 00000000..cc81e391 --- /dev/null +++ b/src/reflect/pointer/PointerTdx.cpp @@ -0,0 +1,17 @@ +/* file PointerTdx.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "pointer/PointerTdx.hpp" + +namespace xo { + namespace reflect { + std::string const & + PointerTdx::struct_member_name(uint32_t i) const { + return TypeDescrExtra::struct_member_name(i); + } /*struct_member_name*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end PointerTdx.cpp */ diff --git a/src/reflect/struct/StructMember.cpp b/src/reflect/struct/StructMember.cpp new file mode 100644 index 00000000..59d69056 --- /dev/null +++ b/src/reflect/struct/StructMember.cpp @@ -0,0 +1,36 @@ +/* file StructMember.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "struct/StructMember.hpp" +#include "indentlog/scope.hpp" +#include + +namespace xo { + using xo::scope; + using xo::xtag; + + namespace reflect { + static_assert(std::is_move_constructible_v); + + TaggedPtr + AbstractStructMemberAccessor::member_tp(void * struct_addr) const + { + //XO_SCOPE(lscope); + + TaggedPtr retval = (this + ->member_td() + ->most_derived_self_tp(this->address(struct_addr))); + + //lscope.log(xtag("self_td", this->struct_td()->short_name()), + // xtag("member_td.declared", this->member_td()->short_name()), + // xtag("member_td.actual", retval.td()->short_name())); + + return retval; + } /*member_tp*/ + + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end StructMember.cpp */ diff --git a/src/reflect/struct/StructTdx.cpp b/src/reflect/struct/StructTdx.cpp new file mode 100644 index 00000000..593ad388 --- /dev/null +++ b/src/reflect/struct/StructTdx.cpp @@ -0,0 +1,55 @@ +/* @file StructTdx.cpp */ + +#include "struct/StructTdx.hpp" + +namespace xo { + using std::uint32_t; + + namespace reflect { + std::unique_ptr + StructTdx::make(std::vector member_v, + bool have_to_self_tp, + std::function to_self_tp) + { + return std::unique_ptr(new StructTdx(std::move(member_v), + have_to_self_tp, + std::move(to_self_tp))); + } /*make*/ + + TaggedPtr + StructTdx::child_tp(uint32_t i, void * object) const + { + if (i >= this->member_v_.size()) { + /* TODO: raise exception here? */ + return TaggedPtr::universal_null(); + } + + StructMember const & member_info = this->member_v_[i]; + + return member_info.get_member_tp(object); + + } /*get_child*/ + + std::string const & + StructTdx::struct_member_name(uint32_t i) const + { + StructMember const * sm = this->struct_member(i); + + return sm->member_name(); + } /*struct_member_name*/ + + StructMember const * + StructTdx::struct_member(uint32_t i) const + { + if (i >= this->member_v_.size()) { + /* TODO: raise exception here */ + assert(false); + return nullptr; + } + + return &(this->member_v_[i]); + } /*struct_member*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end StructTdx.cpp */ diff --git a/src/reflect/vector/VectorTdx.cpp b/src/reflect/vector/VectorTdx.cpp new file mode 100644 index 00000000..fd2e40e4 --- /dev/null +++ b/src/reflect/vector/VectorTdx.cpp @@ -0,0 +1,20 @@ +/* file VectorTdx.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "vector/VectorTdx.hpp" + +namespace xo { + namespace reflect { + std::string const & + VectorTdx::struct_member_name(uint32_t i) const { + return TypeDescrExtra::struct_member_name(i); + } /*struct_member_name*/ + + } /*namespace reflect*/ + +} /*namespace xo*/ + + +/* end VectorTdx.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..80ce49ac --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,57 @@ +# build unittest reflect/utest + +set(SELF_EXECUTABLE_NAME utest.reflect) +set(SELF_SOURCE_FILES reflect_utest_main.cpp StructReflector.test.cpp VectorTdx.test.cpp StructTdx.test.cpp) + +add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) +xo_include_options(${SELF_EXECUTABLE_NAME}) + +add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) +target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) + +# ---------------------------------------------------------------- +# generic project dependency + +# PROJECT_SOURCE_DIR: +# so we can for example write +# #include "indentlog/scope.hpp" +# from anywhere in the project +# PROJECT_BINARY_DIR: +# since version file will be in build directory, need that directory +# to also be included in compiler's include path +# +target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC + ${PROJECT_SOURCE_DIR} + ${PROJECT_BINARY_DIR}) + +# ---------------------------------------------------------------- +# internal dependencies: logutil, ... + +target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC reflect) + +# ---------------------------------------------------------------- +# 3rd part dependency: catch2: + +find_package(Catch2 2 REQUIRED) + +# need this so that catch2/include appears in compile_commands.json, +# on which lsp integration relies. +# +# See also /nix/store/*-catch2-*/lib/cmake/Catch2/ParseAndAddCatchTests.cmake; +# commands here derived from ^ .cmake file +# +#find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") +#target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC ${CATCH_INCLUDE_DIR}) + +# ---------------------------------------------------------------- +# make standard directories for std:: includes explicit +# so that +# (1) they appear in compile_commands.json. +# (2) clangd (run from emacs lsp-mode) can find them +# +if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +endif() + +# end CMakeLists.txt diff --git a/utest/StructReflector.test.cpp b/utest/StructReflector.test.cpp new file mode 100644 index 00000000..5bf87ade --- /dev/null +++ b/utest/StructReflector.test.cpp @@ -0,0 +1,142 @@ +/* file StructReflector.test.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "reflect/Reflect.hpp" +#include "reflect/StructReflector.hpp" +#include + +#define STRINGIFY(x) #x + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::StructReflector; + using xo::reflect::Reflect; + + namespace ut { + namespace { + struct TestStruct0 {}; + struct TestStruct1 {}; + } + + TEST_CASE("struct-reflect-empty", "[reflect]") { + StructReflector sr; + + REQUIRE(Reflect::is_reflected() == false); + REQUIRE(Reflect::is_reflected() == true); + + TestStruct0 recd0; + TaggedPtr tp = Reflect::make_tp(&recd0); + + REQUIRE(tp.address() == &recd0); + REQUIRE(tp.td() == Reflect::require()); + + REQUIRE(tp.n_child() == 0); + + REQUIRE(tp.get_child(0).is_universal_null()); + REQUIRE(tp.get_child(0).td() == nullptr); + REQUIRE(tp.get_child(0).address() == nullptr); + } /*TEST_CASE(struct-reflect-empty)*/ + + namespace { + struct TestStructS1 { int x_; }; + } + + TEST_CASE("struct-reflect-s1", "[reflect]") { + StructReflector sr; + + REQUIRE(Reflect::is_reflected() == true); + + //sr.reflect_member(STRINGIFY(x_), &decltype(sr)::struct_t::x_); + REFLECT_LITERAL_MEMBER(sr, x_); + + REQUIRE(!Reflect::require()->is_struct()); + + sr.require_complete(); + + REQUIRE(Reflect::require()->is_struct()); + } /*TEST_CASE(struct-reflect-s1)*/ + + namespace { + struct TestStructS2 { int x_; }; + } + + TEST_CASE("struct-reflect-s2", "[reflect]") { + StructReflector sr; + + REQUIRE(Reflect::is_reflected() == true); + + //sr.reflect_member(STRINGIFY(x_), &decltype(sr)::struct_t::x_); + REFLECT_MEMBER(sr, x); + + REQUIRE(!Reflect::require()->is_struct()); + + sr.require_complete(); + + REQUIRE(Reflect::require()->is_struct()); + + TestStructS2 recd1{666}; + + TaggedPtr tp = Reflect::make_tp(&recd1); + + REQUIRE(tp.address() == &recd1); + REQUIRE(tp.td() == Reflect::require()); + + REQUIRE(tp.n_child() == 1); + + REQUIRE(tp.get_child(0).td() == Reflect::require()); + REQUIRE(tp.get_child(0).address() == &(recd1.x_)); + + REQUIRE(tp.get_child(1).is_universal_null()); + } /*TEST_CASE(struct-reflect-s2)*/ + + namespace { + struct TestStructS3 { int x_; char y_; double z_; }; + } + + TEST_CASE("struct-reflect-s3", "[reflect]") { + StructReflector sr; + + REQUIRE(Reflect::is_reflected() == true); + + REFLECT_MEMBER(sr, x); + REFLECT_MEMBER(sr, y); + REFLECT_MEMBER(sr, z); + + REQUIRE(!Reflect::require()->is_struct()); + + sr.require_complete(); + + REQUIRE(Reflect::require()->is_struct()); + + /* verify we can traverse reflected instances */ + TestStructS3 recd1{666, 'Y', -1.234}; + + TaggedPtr tp = Reflect::make_tp(&recd1); + + REQUIRE(tp.address() == &recd1); + REQUIRE(tp.td() == Reflect::require()); + + REQUIRE(tp.n_child() == 3); + + REQUIRE(tp.get_child(0).td() == Reflect::require()); + REQUIRE(tp.get_child(0).address() == &(recd1.x_)); + + REQUIRE(tp.get_child(1).td() == Reflect::require()); + REQUIRE(tp.get_child(1).address() == &(recd1.y_)); + + REQUIRE(tp.get_child(2).td() == Reflect::require()); + REQUIRE(tp.get_child(2).address() == &(recd1.z_)); + + REQUIRE(tp.get_child(3).is_universal_null()); + REQUIRE(tp.get_child(3).td() == nullptr); + REQUIRE(tp.get_child(3).address() == nullptr); + + } /*TEST_CASE(struct-reflect-s3)*/ + } /*namespace ut */ +} /*namespace xo*/ + + +/* end StructReflector.test.cpp */ diff --git a/utest/StructTdx.test.cpp b/utest/StructTdx.test.cpp new file mode 100644 index 00000000..c20e2139 --- /dev/null +++ b/utest/StructTdx.test.cpp @@ -0,0 +1,59 @@ +/* file StructTdx.test.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "reflect/Reflect.hpp" +#include + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::TypeDescr; + using xo::reflect::Metatype; + + namespace ut { + TEST_CASE("std-pair-reflect", "[reflect]") { + std::pair p; + + TaggedPtr tp = Reflect::make_tp(&p); + //TypeDescr td = Reflect::require>(); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &p); + REQUIRE(tp.is_struct()); + REQUIRE(tp.is_vector() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_struct); + REQUIRE(tp.recover_native>() == &p); + REQUIRE(tp.n_child() == 2); /* struct with 2 members */ + REQUIRE(tp.struct_member_name(0) == "first"); + REQUIRE(tp.struct_member_name(1) == "second"); + + TaggedPtr tp0 = tp.get_child(0); + + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(p.first)); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(p.first)); + REQUIRE(tp0.n_child() == 0); + + TaggedPtr tp1 = tp.get_child(1); + + REQUIRE(tp1.td()->complete_flag()); + REQUIRE(tp1.address() == &(p.second)); + REQUIRE(!tp1.is_vector()); + REQUIRE(!tp1.is_struct()); + REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp1.recover_native() == &(p.second)); + REQUIRE(tp1.n_child() == 0); + + } /*TEST_CASE(std-pair-reflect)*/ + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end VectorTdx.test.cpp */ diff --git a/utest/VectorTdx.test.cpp b/utest/VectorTdx.test.cpp new file mode 100644 index 00000000..3836b4f7 --- /dev/null +++ b/utest/VectorTdx.test.cpp @@ -0,0 +1,181 @@ +/* file VectorTdx.test.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "reflect/Reflect.hpp" +#include + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::TypeDescr; + using xo::reflect::Metatype; + + namespace ut { + TEST_CASE("std-vector-reflect-empty", "[reflect]") { + std::vector v; + + TaggedPtr tp = Reflect::make_tp(&v); + //TypeDescr td = Reflect::require>(); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 0); /*since empty vector*/ + // REQUIRE(tp.child_td(0) == ... + } /*TEST_CASE(std-vector-reflect-empty)*/ + + TEST_CASE("std-vector-reflect-one", "[reflect]") { + std::vector v = { 1.123 }; + + TaggedPtr tp = Reflect::make_tp(&v); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 1); + + TaggedPtr tp0 = tp.get_child(0); + + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); + } /*TEST_CASE(std-vector-reflect-one)*/ + + TEST_CASE("std-vector-reflect-two", "[reflect]") { + std::vector v = { 1.123, 2.234 }; + + TaggedPtr tp = Reflect::make_tp(&v); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 2); + + TaggedPtr tp0 = tp.get_child(0); + + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); + + TaggedPtr tp1 = tp.get_child(1); + + REQUIRE(tp1.td()->complete_flag()); + REQUIRE(tp1.address() == &(v[1])); + REQUIRE(!tp1.is_vector()); + REQUIRE(!tp1.is_struct()); + REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp1.recover_native() == &(v[1])); + REQUIRE(tp1.n_child() == 0); + } /*TEST(std-vector-reflect-two)*/ + + // ----- std::array ----- + + TEST_CASE("std-array-reflect-empty", "[reflect]") { + std::array v; + + TaggedPtr tp = Reflect::make_tp(&v); + //TypeDescr td = Reflect::require>(); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 0); /*since empty vector*/ + // REQUIRE(tp.child_td(0) == ... + } /*TEST_CASE(std-array-reflect-empty)*/ + + TEST_CASE("std-array-reflect-one", "[reflect]") { + std::array v = { 1.123 }; + + TaggedPtr tp = Reflect::make_tp(&v); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 1); + + TaggedPtr tp0 = tp.get_child(0); + + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); + } /*TEST_CASE(std-array-reflect-one)*/ + + TEST_CASE("std-array-reflect-two", "[reflect]") { + std::array v = { 1.123, 2.234 }; + + TaggedPtr tp = Reflect::make_tp(&v); + + REQUIRE(Reflect::is_reflected>() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 2); + + TaggedPtr tp0 = tp.get_child(0); + + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); + + TaggedPtr tp1 = tp.get_child(1); + + REQUIRE(tp1.td()->complete_flag()); + REQUIRE(tp1.address() == &(v[1])); + REQUIRE(!tp1.is_vector()); + REQUIRE(!tp1.is_struct()); + REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp1.recover_native() == &(v[1])); + REQUIRE(tp1.n_child() == 0); + } /*TEST(std-array-reflect-two)*/ + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end VectorTdx.test.cpp */ diff --git a/utest/reflect_utest_main.cpp b/utest/reflect_utest_main.cpp new file mode 100644 index 00000000..91c62d09 --- /dev/null +++ b/utest/reflect_utest_main.cpp @@ -0,0 +1,6 @@ +/* file reflect_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end reflect_utest_main.cpp */ From ab23f431af88de666c69912f76e88514bffbab91 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 25 Sep 2023 17:55:04 -0400 Subject: [PATCH 0032/2524] + build/install notes --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 27fe0373..fb6682d4 100644 --- a/README.md +++ b/README.md @@ -1 +1,19 @@ # reflection library + +### build + install +``` +$ cd reflect +$ mkdir build +$ cd build +$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` + +### build for unit test coverage +``` +$ cd refcnt +$ mkdir build-ccov +$ cd build-ccov +$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` From 57808cc6142a237e65d2f9893364dc8a3db9a2c3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 25 Sep 2023 17:56:57 -0400 Subject: [PATCH 0033/2524] + .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6637741b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +build*/* From 4cc5f4049fc96cdf96b2aabda438bbea5bb95900 Mon Sep 17 00:00:00 2001 From: Roland Date: Mon, 25 Sep 2023 17:59:47 -0400 Subject: [PATCH 0034/2524] Create cmake-single-platform.yml --- .github/workflows/cmake-single-platform.yml | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/cmake-single-platform.yml diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml new file mode 100644 index 00000000..28c6f783 --- /dev/null +++ b/.github/workflows/cmake-single-platform.yml @@ -0,0 +1,39 @@ +# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml +name: CMake on a single platform + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} + From 94788b53f0a14d3389bc9ba88dcd45402f812cbe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 25 Sep 2023 18:05:44 -0400 Subject: [PATCH 0035/2524] github: action for git submodules, take 1 --- .github/workflows/cmake-single-platform.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 28c6f783..a0fdcdc6 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -22,6 +22,10 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Update submodules + # update/fetch submodules (attached as top-level children of reflect/repo/) + run: git submodule update --init --recursive + - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type @@ -36,4 +40,3 @@ jobs: # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} - From 31e0c14930b4643ef7021ea3f993951e175aa1ed Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 25 Sep 2023 18:14:10 -0400 Subject: [PATCH 0036/2524] github: submodule actions, take 2 --- .github/workflows/cmake-single-platform.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index a0fdcdc6..8cda1cd7 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -20,11 +20,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - - name: Update submodules - # update/fetch submodules (attached as top-level children of reflect/repo/) - run: git submodule update --init --recursive + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: true - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. From d1833c5fa60c76d380ce1a914bf8dd5ec863a955 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 25 Sep 2023 18:16:20 -0400 Subject: [PATCH 0037/2524] github: apt-get catch2 dependency --- .github/workflows/cmake-single-platform.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 8cda1cd7..663a266b 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -20,6 +20,9 @@ jobs: runs-on: ubuntu-latest steps: + - name: Install catch2 + run: sudo apt-get install -y catch2 + - name: Checkout uses: actions/checkout@v3 with: From 8f36e73b18c22ee4cd57caaa985360213e006ebe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 01:27:13 -0400 Subject: [PATCH 0038/2524] build: remove submodules (moved to reflect-sm project) --- .gitmodules | 9 ---- CMakeLists.txt | 85 -------------------------------------- repo/README.md | 5 --- repo/refcnt | 1 - repo/subsys | 1 - src/reflect/CMakeLists.txt | 6 ++- 6 files changed, 4 insertions(+), 103 deletions(-) delete mode 100644 .gitmodules delete mode 100644 repo/README.md delete mode 160000 repo/refcnt delete mode 160000 repo/subsys diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index ebcf3a27..00000000 --- a/.gitmodules +++ /dev/null @@ -1,9 +0,0 @@ -[submodule "repo/indentlog"] - path = repo/indentlog - url = git@github.com:Rconybea/indentlog.git -[submodule "repo/refcnt"] - path = repo/refcnt - url = git@github.com:Rconybea/refcnt.git -[submodule "repo/subsys"] - path = repo/subsys - url = git@github.com:Rconybea/subsys.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f777c59..078681e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,91 +41,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") -# ---------------------------------------------------------------- -# external projects (need these to exist before add_subdirectory() below) -# -# we are expecting these projects to coexist peacefully in build/local -# (i.e. can run their `make install` steps independently with prefix build/local, -# without any collisions) -# - -include(ExternalProject) - -## ----- indentlog ------ - -# NOTE: we could have cmake handle git interaction, -# but we want source for certain dependencies to live in a location -# that's suitable for accepting changes + coordinated commits. -# In particular, not in the build directory! -# -externalproject_add( - project_indentlog - SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/indentlog - BINARY_DIR ${PROJECT_BINARY_DIR}/ext/indentlog - INSTALL_DIR ${PROJECT_BINARY_DIR}/local - CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= - BUILD_COMMAND make - INSTALL_COMMAND make install - TEST_BEFORE_INSTALL True -) - -add_library(indentlog INTERFACE IMPORTED) -#set_property(TARGET indentlog PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/local/lib/libindentlog.so) -add_dependencies(indentlog project_indentlog) - -# runs ctest in indentlog build dir -add_test(NAME indentlog COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/indentlog) -#target_code_coverage(indentlog EXTERNAL AUTO ALL) - -# ----- subsys ----- - -externalproject_add( - project_subsys - SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/subsys - BINARY_DIR ${PROJECT_BINARY_DIR}/ext/subsys - INSTALL_DIR ${PROJECT_BINARY_DIR}/local - CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= - BUILD_COMMAND make - INSTALL_COMMAND make install - TEST_BEFORE_INSTALL True -) - -add_library(subsys INTERFACE IMPORTED) -add_dependencies(subsys project_subsys) - -# runs ctest in subsys build dir -add_test(NAME subsys COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/subsys) - -# ----- refcnt ----- - -# CMAKE_ARGS -# CMAKE_BUILD_TYPE propagate Debug/Release build type -# CODE_COVERAGE propagate code coverage setting -# CMAKE_PREFIX_PATH path for support cmake files of dependencies (needed for find_package() to work) -# CMAKE_INSTALL_PREFIX install subproject here -# SOURCE_DIR -- where to find already established source code -# BINARY_DIR -- run build for external project here -# INSTALL_DIR -- (temporarily) install external project here -# -externalproject_add( - project_refcnt - SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/refcnt - BINARY_DIR ${PROJECT_BINARY_DIR}/ext/refcnt - INSTALL_DIR ${PROJECT_BINARY_DIR}/local - CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= - BUILD_COMMAND make - INSTALL_COMMAND make install - TEST_BEFORE_INSTALL True -) - -add_library(refcnt SHARED IMPORTED) -set_property(TARGET refcnt PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/local/lib/librefcnt.so) -add_dependencies(refcnt project_refcnt) -add_dependencies(refcnt project_indentlog) - -# runs ctest in refcnt build dir -add_test(NAME refcnt COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/refcnt) - # ---------------------------------------------------------------- # sources diff --git a/repo/README.md b/repo/README.md deleted file mode 100644 index 3acdcde4..00000000 --- a/repo/README.md +++ /dev/null @@ -1,5 +0,0 @@ - -``` -cd reflect/repo -git submodule add git@github.com/someusername/someproject.git -``` diff --git a/repo/refcnt b/repo/refcnt deleted file mode 160000 index 4b3c854f..00000000 --- a/repo/refcnt +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4b3c854fc7872ba404ce4e0af25ecfdc47822ae1 diff --git a/repo/subsys b/repo/subsys deleted file mode 160000 index 1c384e1f..00000000 --- a/repo/subsys +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1c384e1ff0a01885bdf0713be858085814b6ece6 diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index e1c1f65f..799cd10f 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -25,7 +25,8 @@ xo_install_library(${SELF_LIBRARY_NAME}) #xo_refcnt_dependency(${SELF_LIBRARY_NAME}) #xo_indentlog_dependency(${SELF_LIBRARY_NAME}) -add_dependencies(${SELF_LIBRARY_NAME} refcnt) +find_package(refcnt REQUIRED) +#add_dependencies(${SELF_LIBRARY_NAME} refcnt) target_include_directories( ${SELF_LIBRARY_NAME} PUBLIC $ @@ -33,7 +34,8 @@ target_include_directories( ) target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) -add_dependencies(${SELF_LIBRARY_NAME} indentlog) +find_package(indentlog REQUIRED) +#add_dependencies(${SELF_LIBRARY_NAME} indentlog) # note: can't use find_package() here, # because find_package() needs to run successfully before # dependency gets installed. From 129e762c7b6ea783425544fdfcd8bb52800f0112 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 01:32:50 -0400 Subject: [PATCH 0039/2524] build: + CMAKE_INSTALL_RPATH + remov externalproject_add() calls --- CMakeLists.txt | 87 ++------------------------------------------------ 1 file changed, 3 insertions(+), 84 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f777c59..dc1646f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,90 +41,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") -# ---------------------------------------------------------------- -# external projects (need these to exist before add_subdirectory() below) -# -# we are expecting these projects to coexist peacefully in build/local -# (i.e. can run their `make install` steps independently with prefix build/local, -# without any collisions) -# - -include(ExternalProject) - -## ----- indentlog ------ - -# NOTE: we could have cmake handle git interaction, -# but we want source for certain dependencies to live in a location -# that's suitable for accepting changes + coordinated commits. -# In particular, not in the build directory! -# -externalproject_add( - project_indentlog - SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/indentlog - BINARY_DIR ${PROJECT_BINARY_DIR}/ext/indentlog - INSTALL_DIR ${PROJECT_BINARY_DIR}/local - CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= - BUILD_COMMAND make - INSTALL_COMMAND make install - TEST_BEFORE_INSTALL True -) - -add_library(indentlog INTERFACE IMPORTED) -#set_property(TARGET indentlog PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/local/lib/libindentlog.so) -add_dependencies(indentlog project_indentlog) - -# runs ctest in indentlog build dir -add_test(NAME indentlog COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/indentlog) -#target_code_coverage(indentlog EXTERNAL AUTO ALL) - -# ----- subsys ----- - -externalproject_add( - project_subsys - SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/subsys - BINARY_DIR ${PROJECT_BINARY_DIR}/ext/subsys - INSTALL_DIR ${PROJECT_BINARY_DIR}/local - CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= - BUILD_COMMAND make - INSTALL_COMMAND make install - TEST_BEFORE_INSTALL True -) - -add_library(subsys INTERFACE IMPORTED) -add_dependencies(subsys project_subsys) - -# runs ctest in subsys build dir -add_test(NAME subsys COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/subsys) - -# ----- refcnt ----- - -# CMAKE_ARGS -# CMAKE_BUILD_TYPE propagate Debug/Release build type -# CODE_COVERAGE propagate code coverage setting -# CMAKE_PREFIX_PATH path for support cmake files of dependencies (needed for find_package() to work) -# CMAKE_INSTALL_PREFIX install subproject here -# SOURCE_DIR -- where to find already established source code -# BINARY_DIR -- run build for external project here -# INSTALL_DIR -- (temporarily) install external project here -# -externalproject_add( - project_refcnt - SOURCE_DIR ${PROJECT_SOURCE_DIR}/repo/refcnt - BINARY_DIR ${PROJECT_BINARY_DIR}/ext/refcnt - INSTALL_DIR ${PROJECT_BINARY_DIR}/local - CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCODE_COVERAGE=${CODE_COVERAGE} -DCMAKE_PREFIX_PATH= -DCMAKE_INSTALL_PREFIX= - BUILD_COMMAND make - INSTALL_COMMAND make install - TEST_BEFORE_INSTALL True -) - -add_library(refcnt SHARED IMPORTED) -set_property(TARGET refcnt PROPERTY IMPORTED_LOCATION ${PROJECT_BINARY_DIR}/local/lib/librefcnt.so) -add_dependencies(refcnt project_refcnt) -add_dependencies(refcnt project_indentlog) - -# runs ctest in refcnt build dir -add_test(NAME refcnt COMMAND ${PROJECT_SOURCE_DIR}/cmake/run-external-ctest ${PROJECT_BINARY_DIR}/ext/refcnt) +if(NOT CMAKE_INSTALL_RPATH) + set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") +endif() # ---------------------------------------------------------------- # sources From 56154d53af8d87f1e2a6f5432335fecd95a41b1c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 01:42:18 -0400 Subject: [PATCH 0040/2524] build: drop repo/indentlog (was submodule w/ a preceding commit) --- repo/indentlog | 1 - 1 file changed, 1 deletion(-) delete mode 160000 repo/indentlog diff --git a/repo/indentlog b/repo/indentlog deleted file mode 160000 index bf92724a..00000000 --- a/repo/indentlog +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bf92724aa5d123b6acb9b00318e579828fb9ff38 From 06a9f72ef5c08df1b21c275fac4fc04d43f0f8b8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 11:43:12 -0400 Subject: [PATCH 0041/2524] github: + checkout/build/install indentlog --- .github/workflows/cmake-single-platform.yml | 23 +++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 663a266b..9de50c13 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -23,21 +23,36 @@ jobs: - name: Install catch2 run: sudo apt-get install -y catch2 - - name: Checkout + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + run: cmake --install ${{github.workspace}}/build_indentlog + + - name: Checkout reflect uses: actions/checkout@v3 with: submodules: true - - name: Configure CMake + - name: Configure reflect # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - - name: Build + - name: Build reflect # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - - name: Test + - name: Test reflect working-directory: ${{github.workspace}}/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail From 955462003b4cb6abf85358795dc4bab7734a796a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 11:46:17 -0400 Subject: [PATCH 0042/2524] github: checkout/build/install subsys dep --- .github/workflows/cmake-single-platform.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 9de50c13..81fbb2eb 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -38,6 +38,21 @@ jobs: - name: Install indentlog run: cmake --install ${{github.workspace}}/build_indentlog + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + run: cmake --install ${{github.workspace}}/build_subsys + - name: Checkout reflect uses: actions/checkout@v3 with: From d1be10ab3a5be4f8aa9f890aa0f97b30083079d6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 11:48:02 -0400 Subject: [PATCH 0043/2524] github: checkout/build/install refcnt dep --- .github/workflows/cmake-single-platform.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 81fbb2eb..df05cf9b 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -53,6 +53,21 @@ jobs: - name: Install subsys run: cmake --install ${{github.workspace}}/build_subsys + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + run: cmake --install ${{github.workspace}}/build_refcnt + - name: Checkout reflect uses: actions/checkout@v3 with: From d7232b4da6a9c06ce24f892c65db0b5da143f6ec Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 11:52:18 -0400 Subject: [PATCH 0044/2524] github: fix installdir for sibling xo deps --- .github/workflows/cmake-single-platform.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index df05cf9b..b921565b 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -70,13 +70,11 @@ jobs: - name: Checkout reflect uses: actions/checkout@v3 - with: - submodules: true - name: Configure reflect # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build reflect # Build your program with the given configuration From 5b4d9331c8c60a69e850617e23970d635aa19537 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 12:32:12 -0400 Subject: [PATCH 0045/2524] build: provide typical default value for CMAKE_INSTALL_RPATH --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ed3c4486..a0cdb52c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,6 @@ 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. @@ -39,6 +38,10 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") +if(NOT CMAKE_INSTALL_RPATH) + set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") +endif() + # ---------------------------------------------------------------- # sources From 5059252204a534ee8340d0e06a9beafd0b54ba1e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 13:00:46 -0400 Subject: [PATCH 0046/2524] github: use specific build directory for reflect --- .github/workflows/cmake-single-platform.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index b921565b..70826978 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -23,6 +23,7 @@ jobs: - name: Install catch2 run: sudo apt-get install -y catch2 + - name: Clone indentlog uses: actions/checkout@v3 with: @@ -38,6 +39,7 @@ jobs: - name: Install indentlog run: cmake --install ${{github.workspace}}/build_indentlog + - name: Clone subsys uses: actions/checkout@v3 with: @@ -47,6 +49,7 @@ jobs: - name: Configure subsys run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + - name: Build subsys run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} @@ -59,6 +62,7 @@ jobs: repository: Rconybea/refcnt path: repo/refcnt + - name: Configure refcnt run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt @@ -71,17 +75,19 @@ jobs: - name: Checkout reflect uses: actions/checkout@v3 + + - name: Configure reflect # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build reflect # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} - name: Test reflect - working-directory: ${{github.workspace}}/build + working-directory: ${{github.workspace}}/build_reflect # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} From e636c59f62b47eecce81d85aaa65f3b628d385d8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 13:04:24 -0400 Subject: [PATCH 0047/2524] github: try --debug-find --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 70826978..a9b9ac4f 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -80,7 +80,7 @@ jobs: - name: Configure reflect # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find - name: Build reflect # Build your program with the given configuration From ce7154db57cf13456798158fad570da4fd05b830 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 13:44:21 -0400 Subject: [PATCH 0048/2524] build: use cmake find_package() in CONFIG mode --- src/reflect/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index 799cd10f..af38df15 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -25,7 +25,7 @@ xo_install_library(${SELF_LIBRARY_NAME}) #xo_refcnt_dependency(${SELF_LIBRARY_NAME}) #xo_indentlog_dependency(${SELF_LIBRARY_NAME}) -find_package(refcnt REQUIRED) +find_package(refcnt CONFIG REQUIRED) #add_dependencies(${SELF_LIBRARY_NAME} refcnt) target_include_directories( ${SELF_LIBRARY_NAME} PUBLIC @@ -34,7 +34,7 @@ target_include_directories( ) target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) -find_package(indentlog REQUIRED) +find_package(indentlog CONFIG REQUIRED) #add_dependencies(${SELF_LIBRARY_NAME} indentlog) # note: can't use find_package() here, # because find_package() needs to run successfully before From 3d8fd35e82f6373076286aae8ae587bfc220dd6b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 14:03:45 -0400 Subject: [PATCH 0049/2524] build: print cmake version --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index dc1646f0..2ddd1ad2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.10) +message(CMAKE_VERSION=${CMAKE_VERSION}) + project(reflect VERSION 0.1) enable_language(CXX) From 4430470f568a44d2b1ec325368cb3a637fe0c33c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 15:08:43 -0400 Subject: [PATCH 0050/2524] github: debug-find while configuring refcnt (cf for reflect) --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index a9b9ac4f..58b3e490 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -64,7 +64,7 @@ jobs: - name: Configure refcnt - run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local --debug-find repo/refcnt - name: Build refcnt run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} From 34b7722410c44568e0420c74af2f8f08b7aa9d1c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 15:13:03 -0400 Subject: [PATCH 0051/2524] github: try triggering indentlog failure --- src/reflect/CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index af38df15..d30bd794 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -46,6 +46,15 @@ target_include_directories( ) target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC indentlog) +find_package(refcnt CONFIG REQUIRED) +#add_dependencies(${SELF_LIBRARY_NAME} refcnt) +target_include_directories( + ${SELF_LIBRARY_NAME} PUBLIC + $ + $ +) +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) + # ---------------------------------------------------------------- # 3rd party dependency: boost: From 8e1b51c96f2e825731e28cc7a48ebbaa6f5b732b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 15:21:02 -0400 Subject: [PATCH 0052/2524] github: experiment with --debug-find --- .github/workflows/cmake-single-platform.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 5d74447d..b8f26927 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -32,6 +32,8 @@ jobs: repository: Rconybea/indentlog path: repo/indentlog + + - name: Configure indentlog # configure cmake for indentlog in dedicated build directory. run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog @@ -43,10 +45,12 @@ jobs: # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_indentlog + + - name: Configure refcnt # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find - name: Build refcnt # Build your program with the given configuration From 75cfc477b8cce8553cf04052d23822cc7c8be811 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 15:45:03 -0400 Subject: [PATCH 0053/2524] github: more experiments w/ --debug-find --- .github/workflows/cmake-single-platform.yml | 6 +++--- src/reflect/CMakeLists.txt | 9 --------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 58b3e490..2271e790 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -31,7 +31,7 @@ jobs: path: repo/indentlog - name: Configure indentlog - run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local --debug-find repo/indentlog - name: Build indentlog run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} @@ -47,7 +47,7 @@ jobs: path: repo/subsys - name: Configure subsys - run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local --debug-find repo/subsys - name: Build subsys @@ -56,13 +56,13 @@ jobs: - name: Install subsys run: cmake --install ${{github.workspace}}/build_subsys + - name: Clone refcnt uses: actions/checkout@v3 with: repository: Rconybea/refcnt path: repo/refcnt - - name: Configure refcnt run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local --debug-find repo/refcnt diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index d30bd794..0590298b 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -25,15 +25,6 @@ xo_install_library(${SELF_LIBRARY_NAME}) #xo_refcnt_dependency(${SELF_LIBRARY_NAME}) #xo_indentlog_dependency(${SELF_LIBRARY_NAME}) -find_package(refcnt CONFIG REQUIRED) -#add_dependencies(${SELF_LIBRARY_NAME} refcnt) -target_include_directories( - ${SELF_LIBRARY_NAME} PUBLIC - $ - $ -) -target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) - find_package(indentlog CONFIG REQUIRED) #add_dependencies(${SELF_LIBRARY_NAME} indentlog) # note: can't use find_package() here, From 8c9b541e7d6530f162cddcff6a382d6a115c9a17 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 15:48:17 -0400 Subject: [PATCH 0054/2524] build: specify CONFIG for find_package(indentlog) --- cmake/cxx.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake index 4f2dd2fa..fd9aed25 100644 --- a/cmake/cxx.cmake +++ b/cmake/cxx.cmake @@ -72,6 +72,7 @@ endmacro() # ---------------------------------------------------------------- # use this for a subdir that builds a library +# and supports find_package() # macro(xo_install_library target) install( @@ -89,7 +90,7 @@ endmacro() # use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers # macro(xo_indentlog_dependency target) - find_package(indentlog REQUIRED) + find_package(indentlog CONFIG REQUIRED) #add_dependencies(${target} indentlog) target_link_libraries(${target} PUBLIC indentlog) #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) From 2bd1b19388a6b6c684be8422b8f8311b73387c55 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 15:55:03 -0400 Subject: [PATCH 0055/2524] github: more weird build experiments --- .github/workflows/cmake-single-platform.yml | 1 - CMakeLists.txt | 3 +++ src/reflect/CMakeLists.txt | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 2271e790..d31e3138 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -80,7 +80,6 @@ jobs: - name: Configure reflect # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find - name: Build reflect # Build your program with the given configuration diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ddd1ad2..b9b28560 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,9 @@ if(NOT CMAKE_INSTALL_RPATH) set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") endif() +# early find_package() experiment +find_package(indentlog CONFIG REQUIRED) + # ---------------------------------------------------------------- # sources diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index 0590298b..dd26ec9c 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -25,7 +25,6 @@ xo_install_library(${SELF_LIBRARY_NAME}) #xo_refcnt_dependency(${SELF_LIBRARY_NAME}) #xo_indentlog_dependency(${SELF_LIBRARY_NAME}) -find_package(indentlog CONFIG REQUIRED) #add_dependencies(${SELF_LIBRARY_NAME} indentlog) # note: can't use find_package() here, # because find_package() needs to run successfully before From 4467586ca059b637cd5fb38e6ac4a264efc3fb2a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 15:57:56 -0400 Subject: [PATCH 0056/2524] github: big revert -- adopt refcnt .yml, try to build up from there --- .github/workflows/cmake-single-platform.yml | 57 +++++---------------- 1 file changed, 14 insertions(+), 43 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index d31e3138..b8f26927 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -20,9 +20,11 @@ jobs: runs-on: ubuntu-latest steps: - - name: Install catch2 - run: sudo apt-get install -y catch2 + - uses: actions/checkout@v3 + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 - name: Clone indentlog uses: actions/checkout@v3 @@ -30,63 +32,32 @@ jobs: repository: Rconybea/indentlog path: repo/indentlog + + - name: Configure indentlog - run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local --debug-find repo/indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog - name: Build indentlog run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} - name: Install indentlog + # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_indentlog - - name: Clone subsys - uses: actions/checkout@v3 - with: - repository: Rconybea/subsys - path: repo/subsys - - - name: Configure subsys - run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local --debug-find repo/subsys - - - - name: Build subsys - run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} - - - name: Install subsys - run: cmake --install ${{github.workspace}}/build_subsys - - - - name: Clone refcnt - uses: actions/checkout@v3 - with: - repository: Rconybea/refcnt - path: repo/refcnt - name: Configure refcnt - run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local --debug-find repo/refcnt - - - name: Build refcnt - run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} - - - name: Install refcnt - run: cmake --install ${{github.workspace}}/build_refcnt - - - name: Checkout reflect - uses: actions/checkout@v3 - - - - - name: Configure reflect # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find - - name: Build reflect + - name: Build refcnt # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} - - name: Test reflect - working-directory: ${{github.workspace}}/build_reflect + - name: Test refcnt + working-directory: ${{github.workspace}}/build_refcnt # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} From 40a894425852b89a4d0620a5d8b513d60f36e4ca Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 16:01:32 -0400 Subject: [PATCH 0057/2524] github: experiment, take 2 --- .github/workflows/cmake-single-platform.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index b8f26927..a57b11a5 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -26,14 +26,13 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + - name: Clone indentlog uses: actions/checkout@v3 with: repository: Rconybea/indentlog path: repo/indentlog - - - name: Configure indentlog # configure cmake for indentlog in dedicated build directory. run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog @@ -46,18 +45,17 @@ jobs: run: cmake --install ${{github.workspace}}/build_indentlog - - - name: Configure refcnt + - name: Configure reflect # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/xyz -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find - - name: Build refcnt + - name: Build reflect # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} - - name: Test refcnt - working-directory: ${{github.workspace}}/build_refcnt + - name: Test reflect + working-directory: ${{github.workspace}}/build_reflect # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} From 7a40acc8b53d8c5d686e0bb049b28c9cb0ecece4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 16:08:21 -0400 Subject: [PATCH 0058/2524] github: experiment 3 --- .github/workflows/cmake-single-platform.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index a57b11a5..7d660c32 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -45,10 +45,10 @@ jobs: run: cmake --install ${{github.workspace}}/build_indentlog - - name: Configure reflect + - name: Configure reflect library # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/xyz -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find - name: Build reflect # Build your program with the given configuration From 031d0bf887edbb6ad46832f8d3b74c35514ab172 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 16:13:10 -0400 Subject: [PATCH 0059/2524] github: experiment 4 --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b9b28560..02c30bf3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ endif() # early find_package() experiment find_package(indentlog CONFIG REQUIRED) +find_package(refcnt2 CONFIG REQUIRED) # ---------------------------------------------------------------- # sources From 0c7f78f721ed0f7afea340b4d1947351f5573ab2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 16:14:36 -0400 Subject: [PATCH 0060/2524] github: experiment 5 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 02c30bf3..cdd7648d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,7 +49,7 @@ endif() # early find_package() experiment find_package(indentlog CONFIG REQUIRED) -find_package(refcnt2 CONFIG REQUIRED) +find_package(refcnt CONFIG REQUIRED) # ---------------------------------------------------------------- # sources From 6e43bb55f04649faea813dff48fe48c443f3c2d3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 16:16:38 -0400 Subject: [PATCH 0061/2524] github: fetch+install refcnt again --- .github/workflows/cmake-single-platform.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 7d660c32..95d1b2b5 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -45,6 +45,24 @@ jobs: run: cmake --install ${{github.workspace}}/build_indentlog + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + - name: Configure reflect library # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 63d5267913b3157ba756c5ddd284c84ca325f5e4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 16:18:54 -0400 Subject: [PATCH 0062/2524] github: fetch+install subsys again --- .github/workflows/cmake-single-platform.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 95d1b2b5..55c366a2 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -45,6 +45,24 @@ jobs: run: cmake --install ${{github.workspace}}/build_indentlog + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + - name: Clone refcnt uses: actions/checkout@v3 with: From efb1b9aa473fd87e4f9211549bc62000016da1ec Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 16:23:46 -0400 Subject: [PATCH 0063/2524] github: try restoring refcnt CMAKE_PREFIX_PATH argument --- .github/workflows/cmake-single-platform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 55c366a2..24e4b579 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -71,7 +71,7 @@ jobs: - name: Configure refcnt # configure cmake for refcnt in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt - name: Build refcnt run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} From d349796363163419026d7ced0f46f3702ba4a2df Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 26 Sep 2023 17:11:57 -0400 Subject: [PATCH 0064/2524] initial commit --- .gitignore | 1 + CMakeLists.txt | 17 +++++++++++++++++ README.md | 21 +++++++++++++++++++++ cmake/xo_cxx.cmake | 23 +++++++++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/xo_cxx.cmake diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..b1480c47 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.10) + +project(xo_macros VERSION 1.0) + +# if any are useful for this project.. +#include (cmake/foo.cmake) + +# ---------------------------------------------------------------- +# cmake export + +set(XO_PROJECT_NAME xo_macros) + +install( + FILES + "cmake/xo_cxx.cmake" + DESTINATION share/cmake/${XO_PROJECT_NAME} +) diff --git a/README.md b/README.md new file mode 100644 index 00000000..a06e0291 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# XO cmake modules + +Collects cmake macros to be shared across XO projects (e.g. indentlog, reflect, kalman, ..) + +## Features + +- support for both manyrepo and monorepo projects +- support for generating cmake `xxxConfig.cmake` files, so cmake `find_package()` works reliably + +## Example + +In some XO project `foo`: +``` +$ cd build +$ cmake -DCMAKE_MODULE_PATH=/usr/local/share/cmake .. +``` + +then in `foo/CMakeLists.txt`: +``` +include(xo_macros/xo_cxx) +``` diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake new file mode 100644 index 00000000..6daf88f9 --- /dev/null +++ b/cmake/xo_cxx.cmake @@ -0,0 +1,23 @@ +set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) +set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) + +macro(xo_copmile_options target) + target_copmile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) +endmacro() + +# dependency on an xo library (including header-only libraries) +# e.g. indentlog +# +# An xo package foo works with cmake +# find_package(foo) +# by providing plugin .cmake files in +# ${PREFIX}/lib/cmake/foo/fooConfig.cmake +# ${PREFIX}/lib/cmake/foo/fooConfigVersion.cmake +# ${PREFIX}/lib/cmake/foo/fooTargets.cmake +# +# dep: name of required dependency, e.g. indentlog +# +macro(xo_internal_dependency target dep) + find_pacakge(${dep} CONFIG REQUIRED) + target_link_libraries(${target} PUBLIC ${dep}) +endmacro() From 5f025d8fc8c12ab8792faf71bc5baa93784efa5a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 09:23:55 -0400 Subject: [PATCH 0065/2524] build: adopt xo-cmake macros as dependency --- .github/workflows/cmake-single-platform.yml | 28 +++++++++++++++++---- CMakeLists.txt | 1 + README.md | 3 ++- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 28c6f783..f654ea9c 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -22,18 +22,36 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Configure CMake + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Configure self (subsys) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - - name: Build + - name: Build self (subsys) # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - - name: Test + - name: Test self (subsys) working-directory: ${{github.workspace}}/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} - diff --git a/CMakeLists.txt b/CMakeLists.txt index c63d6740..4418fe9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.10) project(subsys VERSION 0.1) enable_language(CXX) +include (xo_macros/xo_cxx) include(cmake/cxx.cmake) include(cmake/code-coverage.cmake) diff --git a/README.md b/README.md index b7701781..29b31f1a 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,8 @@ subsys $ cd subsys $ mkdir build $ cd build -$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. $ make $ make install ``` From 870eb57aa79c9ad0369a0471308d0f93df598716 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 09:29:47 -0400 Subject: [PATCH 0066/2524] build: adopt xo-cmake dependency --- .github/workflows/cmake-single-platform.yml | 29 ++++++++++++++++----- CMakeLists.txt | 2 ++ README.md | 14 ++++++++-- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index b8f26927..527d5655 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -26,17 +26,34 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + - name: Clone indentlog uses: actions/checkout@v3 with: repository: Rconybea/indentlog path: repo/indentlog - - - name: Configure indentlog # configure cmake for indentlog in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog - name: Build indentlog run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} @@ -45,12 +62,12 @@ jobs: # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_indentlog + # ---------------------------------------------------------------- - - - name: Configure refcnt + - name: Configure self (refcnt) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build refcnt # Build your program with the given configuration diff --git a/CMakeLists.txt b/CMakeLists.txt index a0cdb52c..c360483c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,8 @@ cmake_minimum_required(VERSION 3.10) project(refcnt VERSION 0.1) enable_language(CXX) +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) include(cmake/cxx.cmake) include(cmake/code-coverage.cmake) diff --git a/README.md b/README.md index 0835e7b9..bae1fb2c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Refcnt is a small shared library supplying intrusive reference counting. ### build + install `indentlog` dependency -see [github/rconybea/indentlog](https://github.com/Rconybea/indentlog) +see [github/Rconybea/indentlog](https://github.com/Rconybea/indentlog) ### copy `refcnt` repository locally ``` @@ -26,11 +26,13 @@ refcnt $ cd refcnt $ mkdir build $ cd build -$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. $ make $ make install ``` +`CMAKE_PREFIX_PATH` should point to prefix where `indentlog` is installed + alternatively, if you're a nix user: ``` $ git clone git@github.com:rconybea/xo-nix.git @@ -40,6 +42,14 @@ $ cd xo-nix $ nix-build -A refcnt ``` +### build for unit test coverage +``` +$ cd refcnt +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + ## Examples ### 1 From ebc70db3e734c8abec8344c2c0c5fe282ae9d037 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 09:39:54 -0400 Subject: [PATCH 0067/2524] build: adopt xo-cmake dependency --- .github/workflows/cmake-single-platform.yml | 35 ++++++++++++++++----- CMakeLists.txt | 2 ++ README.md | 4 +-- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 24e4b579..a8ce3a72 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -26,6 +26,24 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- - name: Clone indentlog uses: actions/checkout@v3 @@ -35,7 +53,7 @@ jobs: - name: Configure indentlog # configure cmake for indentlog in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog - name: Build indentlog run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} @@ -44,6 +62,7 @@ jobs: # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_indentlog + # ---------------------------------------------------------------- - name: Clone subsys uses: actions/checkout@v3 @@ -53,7 +72,7 @@ jobs: - name: Configure subsys # configure cmake for subsys in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys - name: Build subsys run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} @@ -62,6 +81,7 @@ jobs: # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_subsys + # ---------------------------------------------------------------- - name: Clone refcnt uses: actions/checkout@v3 @@ -71,7 +91,7 @@ jobs: - name: Configure refcnt # configure cmake for refcnt in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt - name: Build refcnt run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} @@ -80,17 +100,18 @@ jobs: # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_refcnt + # ---------------------------------------------------------------- - - name: Configure reflect library + - name: Configure self (reflect) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} --debug-find + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - - name: Build reflect + - name: Build self (reflect) # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} - - name: Test reflect + - name: Test self (reflect) working-directory: ${{github.workspace}}/build_reflect # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail diff --git a/CMakeLists.txt b/CMakeLists.txt index cdd7648d..afa760ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,8 @@ message(CMAKE_VERSION=${CMAKE_VERSION}) project(reflect VERSION 0.1) enable_language(CXX) +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) include(cmake/cxx.cmake) include(cmake/code-coverage.cmake) diff --git a/README.md b/README.md index fb6682d4..0cd50f02 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ $ cd reflect $ mkdir build $ cd build -$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. $ make $ make install ``` @@ -15,5 +15,5 @@ $ make install $ cd refcnt $ mkdir build-ccov $ cd build-ccov -$ cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. ``` From aac74e276325b0e6497b80f2125a1b174ac0ef0c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 10:29:15 -0400 Subject: [PATCH 0068/2524] build: adopt xo_cmake dependency --- CMakeLists.txt | 1 + README.md | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f53843ac..16cb2202 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ cmake_minimum_required(VERSION 3.10) project(randomgen VERSION 0.1) enable_language(CXX) +include(xo_macros/xo_cxx) include(cmake/cxx.cmake) include(cmake/code-coverage.cmake) diff --git a/README.md b/README.md index 18300542..905f267e 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,7 @@ $ cd randomgen $ mkdir build $ cd build -$ cmake -DCMAKE_PREFIX_PATH=$(HOME)/local .. +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=$(INSTALL_PREFIX) .. $ make $ make install ``` - -# to build + install to /usr/local (deprecated) - -same as above, but set `CMAKE_PREFIX_PATH` to `/usr/local` From 748c7d86e42af77b56436fae6607f98384c26945 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 10:29:59 -0400 Subject: [PATCH 0069/2524] + .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..69e11d47 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# clangd (run via lsp) keeps state here +.cache +# typical build directory +build From bd3f39693349aa7e6159a5f7384dc2ae667009b1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 12:01:27 -0400 Subject: [PATCH 0070/2524] xo-cmake: + xo_include_options2() --- cmake/xo_cxx.cmake | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 6daf88f9..160a1833 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -1,10 +1,49 @@ + set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) -macro(xo_copmile_options target) +# ---------------------------------------------------------------- +# use this in subdirs that compile c++ code +# +macro(xo_include_options2 target) + # ---------------------------------------------------------------- + # PROJECT_SOURCE_DIR: + # so we can for example write + # #include "ordinaltree/foo.hpp" + # from anywhere in the project + # PROJECT_BINARY_DIR: + # since generated version file will be in build directory, + # need that build directory to also appear in + # compiler's include path + # + target_include_directories( + ${target} PUBLIC + $ # e.g. for #include "indentlog/scope.hpp" + $ + $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ + $ # e.g. for generated .hpp files + ) + + # ---------------------------------------------------------------- + # 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() + +# ---------------------------------------------------------------- +# +macro(xo_compile_options target) target_copmile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) endmacro() +# ---------------------------------------------------------------- +# # dependency on an xo library (including header-only libraries) # e.g. indentlog # @@ -18,6 +57,6 @@ endmacro() # dep: name of required dependency, e.g. indentlog # macro(xo_internal_dependency target dep) - find_pacakge(${dep} CONFIG REQUIRED) + find_package(${dep} CONFIG REQUIRED) target_link_libraries(${target} PUBLIC ${dep}) endmacro() From 3da73f5d3c9beba836ca29c3ed9c6ab22aa6ee2c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 13:18:40 -0400 Subject: [PATCH 0071/2524] xo-cmake: + xo_install_include_tree() --- cmake/xo_cxx.cmake | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 160a1833..d2f95936 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -42,6 +42,13 @@ macro(xo_compile_options target) target_copmile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) endmacro() +# ---------------------------------------------------------------- +# use this to install typical include file subtree +# +macro(xo_install_include_tree) + install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) +endmacro() + # ---------------------------------------------------------------- # # dependency on an xo library (including header-only libraries) From 795d5ddb69ff38ccdb9ffcbe5226af7b194d5ec7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 13:18:59 -0400 Subject: [PATCH 0072/2524] xo-cmake: + xo_install_library2() --- cmake/xo_cxx.cmake | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index d2f95936..6728db82 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -49,6 +49,22 @@ macro(xo_install_include_tree) install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) endmacro() +# ---------------------------------------------------------------- +# use this for a subdir that builds a library +# and supports find_package() +# +macro(xo_install_library2 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() + # ---------------------------------------------------------------- # # dependency on an xo library (including header-only libraries) From 8884d7f9aa55f6a77530f7487a5bec0f24860544 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 13:19:11 -0400 Subject: [PATCH 0073/2524] xo-cmake: + xo_export_cmake_config() --- cmake/xo_cxx.cmake | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 6728db82..8f70a590 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -65,6 +65,40 @@ macro(xo_install_library2 target) ) endmacro() +# ---------------------------------------------------------------- + +# for projectname=foo, require: +# cmake/fooConfig.cmake.in +# +# prepares +# ${PREFIX}/lib/cmake/foo/fooConfig.cmake +# ${PREFIX}/lib/cmake/foo/fooConfigVersion.cmake +# ${PREFIX}/lib/cmake/foo/fooTargets.cmake +# +macro(xo_export_cmake_config projectname projectversion projecttargets) + include(CMakePackageConfigHelpers) + write_basic_package_version_file( + "${PROJECT_BINARY_DIR}/${projectname}ConfigVersion.cmake" + VERSION ${projectversion} + COMPATIBILITY AnyNewerVersion + ) + configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/${projectname}Config.cmake.in" + "${PROJECT_BINARY_DIR}/${projectname}Config.cmake" + INSTALL_DESTINATION lib/cmake/${projectname} + ) + install( + EXPORT ${projecttargets} + DESTINATION lib/cmake/${projectname} + ) + install( + FILES + "${PROJECT_BINARY_DIR}/${projectname}ConfigVersion.cmake" + "${PROJECT_BINARY_DIR}/${projectname}Config.cmake" + DESTINATION lib/cmake/${projectname} + ) +endmacro() + # ---------------------------------------------------------------- # # dependency on an xo library (including header-only libraries) From 155e1e5864969d49d0fb56a4138e33ced3055eb9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 13:20:40 -0400 Subject: [PATCH 0074/2524] subsys: build: use new xo-cmake macros --- CMakeLists.txt | 54 ++++++++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4418fe9b..0c0b9c1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,37 +58,43 @@ target_include_directories(subsys INTERFACE $ $ ) -xo_install_library(subsys) +xo_install_library2(subsys) # ---------------------------------------------------------------- -# cmake export -# (so this library works with cmake's find_package()) +# provide find_package() support -set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") -set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -include(CMakePackageConfigHelpers) -write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" - VERSION 0.1 - COMPATIBILITY AnyNewerVersion -) - -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} - ) - -install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) -install( - FILES - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" - DESTINATION lib/cmake/${XO_PROJECT_NAME}) +## ---------------------------------------------------------------- +## cmake export +## (so this library works with cmake's find_package()) +# +#set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") +#set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") +# +#include(CMakePackageConfigHelpers) +#write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" +# VERSION 0.1 +# COMPATIBILITY AnyNewerVersion +#) +# +#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} +# ) +# +#install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) +#install( +# FILES +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" +# DESTINATION lib/cmake/${XO_PROJECT_NAME}) # ---------------------------------------------------------------- # install .hpp -install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) +xo_install_include_tree() +#install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) # end CMakeLists.txt From 6c56ed4aecbf0972d22d2ecdc19c37626e3fc2fc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 13:21:27 -0400 Subject: [PATCH 0075/2524] refcnt: build: use new xo-make macros --- CMakeLists.txt | 64 ++++++++++++++++++++----------------- cmake/refcntConfig.cmake.in | 2 +- src/CMakeLists.txt | 5 +-- 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c360483c..7f7971f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,41 +53,45 @@ add_subdirectory(utest) # ---------------------------------------------------------------- # cmake export -set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") -set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -include(CMakePackageConfigHelpers) -write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" - VERSION 0.1 - COMPATIBILITY AnyNewerVersion -) - -#install( -# TARGETS ${XO_PROJECT_NAME} -# EXPORT ${XO_PROJECT_NAME}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 +#set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") +#set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") +# +#include(CMakePackageConfigHelpers) +#write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" +# VERSION 0.1 +# COMPATIBILITY AnyNewerVersion +#) +# +##install( +## TARGETS ${XO_PROJECT_NAME} +## EXPORT ${XO_PROJECT_NAME}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 +## ) +# +#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} # ) - -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} - ) - -install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) -install( - FILES - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" - "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" - DESTINATION lib/cmake/${XO_PROJECT_NAME}) +# +#install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) +#install( +# FILES +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" +# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" +# DESTINATION lib/cmake/${XO_PROJECT_NAME}) # ---------------------------------------------------------------- # install .hpp -install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) +xo_install_include_tree() + +#install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) # end CMakeLists.txt diff --git a/cmake/refcntConfig.cmake.in b/cmake/refcntConfig.cmake.in index e13a2c54..9c15f36a 100644 --- a/cmake/refcntConfig.cmake.in +++ b/cmake/refcntConfig.cmake.in @@ -1,4 +1,4 @@ @PACKAGE_INIT@ -include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8083cee0..f58170e8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,8 +7,9 @@ set_target_properties(${SELF_LIBRARY_NAME} VERSION ${PROJECT_VERSION} SOVERSION 1) -xo_indentlog_dependency(${SELF_LIBRARY_NAME}) +xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) +#xo_indentlog_dependency(${SELF_LIBRARY_NAME}) -xo_include_options(${SELF_LIBRARY_NAME}) +xo_include_options2(${SELF_LIBRARY_NAME}) xo_compile_options(${SELF_LIBRARY_NAME}) xo_install_library(${SELF_LIBRARY_NAME}) From eafb897dfd0986f4643c838d9f644410ff00572b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 13:22:02 -0400 Subject: [PATCH 0076/2524] reflect: build: use new xo-cmake macros --- CMakeLists.txt | 92 ++++++++++++++++++++------------------ src/reflect/CMakeLists.txt | 34 +++++++------- 2 files changed, 68 insertions(+), 58 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index afa760ec..d2718ef2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,52 +60,58 @@ add_subdirectory(src/reflect) add_subdirectory(utest) # ---------------------------------------------------------------- -# cmake export: +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +## ---------------------------------------------------------------- +## 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 # -# 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 ..) +#set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") +#set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") # -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}) +#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) +xo_install_include_tree() +#install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/reflect/ DESTINATION include/reflect) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index dd26ec9c..d4b772e4 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -16,12 +16,18 @@ set_target_properties(${SELF_LIBRARY_NAME} # #target_compile_options(${SELF_LIBRARY_NAME} PRIVATE -Werror -Wall -Wextra) xo_compile_options(${SELF_LIBRARY_NAME}) -xo_include_options(${SELF_LIBRARY_NAME}) +xo_include_options2(${SELF_LIBRARY_NAME}) xo_install_library(${SELF_LIBRARY_NAME}) # ---------------------------------------------------------------- # dependencies: logutil, ... +#target_include_directories( +# ${SELF_LIBRARY_NAME} PUBLIC +# $ +# $ +#) + #xo_refcnt_dependency(${SELF_LIBRARY_NAME}) #xo_indentlog_dependency(${SELF_LIBRARY_NAME}) @@ -29,21 +35,19 @@ xo_install_library(${SELF_LIBRARY_NAME}) # note: can't use find_package() here, # because find_package() needs to run successfully before # dependency gets installed. -target_include_directories( - ${SELF_LIBRARY_NAME} PUBLIC - $ - $ -) -target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC indentlog) +xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) -find_package(refcnt CONFIG REQUIRED) -#add_dependencies(${SELF_LIBRARY_NAME} refcnt) -target_include_directories( - ${SELF_LIBRARY_NAME} PUBLIC - $ - $ -) -target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) +#target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC indentlog) + +xo_internal_dependency(${SELF_LIBRARY_NAME} refcnt) +#find_package(refcnt CONFIG REQUIRED) +##add_dependencies(${SELF_LIBRARY_NAME} refcnt) +#target_include_directories( +# ${SELF_LIBRARY_NAME} PUBLIC +# $ +# $ +#) +#target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) # ---------------------------------------------------------------- # 3rd party dependency: boost: From 79bb624ec8bf5199b281bfc48d77aaf27b9646fa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 14:03:55 -0400 Subject: [PATCH 0077/2524] reflect: build: retire superseded cmake macros --- cmake/cxx.cmake | 113 +++++++++++++++++++------------------ src/reflect/CMakeLists.txt | 2 +- utest/CMakeLists.txt | 2 +- 3 files changed, 59 insertions(+), 58 deletions(-) diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake index fa6efb89..4084bac9 100644 --- a/cmake/cxx.cmake +++ b/cmake/cxx.cmake @@ -1,36 +1,36 @@ -# ---------------------------------------------------------------- -# use this in subdirs that compile c++ code +## ---------------------------------------------------------------- +## use this in subdirs that compile c++ code +## +#macro(xo_include_options target) +# # ---------------------------------------------------------------- +# # PROJECT_SOURCE_DIR: +# # so we can for example write +# # #include "ordinaltree/foo.hpp" +# # from anywhere in the project +# # PROJECT_BINARY_DIR: +# # since generated version file will be in build directory, +# # need that build directory to also appear in +# # compiler's include path +# # +# target_include_directories( +# ${target} PUBLIC +# $ # e.g. for #include "indentlog/scope.hpp" +# $ +# $ # e.g. for #include "Refcounted.hpp" in refcnt/src +# $ +# $ # e.g. for generated config.hpp file +# ) # -macro(xo_include_options target) - # ---------------------------------------------------------------- - # PROJECT_SOURCE_DIR: - # so we can for example write - # #include "ordinaltree/foo.hpp" - # from anywhere in the project - # PROJECT_BINARY_DIR: - # since generated version file will be in build directory, - # need that build directory to also appear in - # compiler's include path - # - target_include_directories( - ${target} PUBLIC - $ # e.g. for #include "indentlog/scope.hpp" - $ - $ # e.g. for #include "Refcounted.hpp" in refcnt/src - $ - $ # e.g. for generated config.hpp file - ) - - # ---------------------------------------------------------------- - # make standard directories for std:: includes explicit - # so that - # (1) they appear in compile_commands.json. - # (2) clangd (run from emacs lsp-mode) can find them - # - if(CMAKE_EXPORT_COMPILE_COMMANDS) - set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) - endif() -endmacro() +# # ---------------------------------------------------------------- +# # 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 @@ -70,29 +70,30 @@ 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() +## ---------------------------------------------------------------- +## 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() # -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 +## ---------------------------------------------------------------- +## 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() # -macro(xo_indentlog_dependency target) - find_package(indentlog REQUIRED) - #add_dependencies(${target} indentlog) - target_link_libraries(${target} PUBLIC indentlog) - #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) -endmacro() diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index d4b772e4..bf709478 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -17,7 +17,7 @@ set_target_properties(${SELF_LIBRARY_NAME} #target_compile_options(${SELF_LIBRARY_NAME} PRIVATE -Werror -Wall -Wextra) xo_compile_options(${SELF_LIBRARY_NAME}) xo_include_options2(${SELF_LIBRARY_NAME}) -xo_install_library(${SELF_LIBRARY_NAME}) +xo_install_library2(${SELF_LIBRARY_NAME}) # ---------------------------------------------------------------- # dependencies: logutil, ... diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 80ce49ac..7eae2825 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -4,7 +4,7 @@ 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}) +xo_include_options2(${SELF_EXECUTABLE_NAME}) add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) From dbb9f38e8d2a6ceb9bfdd3bc4909f1390d77088c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:06:18 -0400 Subject: [PATCH 0078/2524] xo-cmake: + xo_toplevel_compile_options() --- cmake/xo_cxx.cmake | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 160a1833..dac88151 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -1,6 +1,32 @@ -set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) -set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) +macro(xo_toplevel_compile_options) + # ---------------------------------------------------------------- + # 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() + + set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) + # XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON + # + # address sanitizer build complains about _FORTIFY_SOURCE redefines + # In file included from :460: + # :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] + # #define _FORTIFY_SOURCE 2 + # + set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) + + if(XO_ADDRESS_SANITIZE) + set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) + else() + set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) + endif() +endif() # ---------------------------------------------------------------- # use this in subdirs that compile c++ code @@ -38,6 +64,9 @@ endmacro() # ---------------------------------------------------------------- # +# Require: +# needs to be preceded by call to xo_toplevel_compile_options() +# macro(xo_compile_options target) target_copmile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) endmacro() From 7a58d584700d106de9d1efad40e7000f20537a44 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:08:43 -0400 Subject: [PATCH 0079/2524] reflect: build: use xo_toplevel_compile_options() to simplify --- CMakeLists.txt | 3 ++ cmake/cxx.cmake | 122 ++++++++++++------------------------------------ 2 files changed, 33 insertions(+), 92 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d2718ef2..7b0240d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,10 +45,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") +xo_toplevel_compile_options() + if(NOT CMAKE_INSTALL_RPATH) set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") endif() + # early find_package() experiment find_package(indentlog CONFIG REQUIRED) find_package(refcnt CONFIG REQUIRED) diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake index 4084bac9..ede44e35 100644 --- a/cmake/cxx.cmake +++ b/cmake/cxx.cmake @@ -1,99 +1,37 @@ ## ---------------------------------------------------------------- -## use this in subdirs that compile c++ code -## -#macro(xo_include_options target) -# # ---------------------------------------------------------------- -# # PROJECT_SOURCE_DIR: -# # so we can for example write -# # #include "ordinaltree/foo.hpp" -# # from anywhere in the project -# # PROJECT_BINARY_DIR: -# # since generated version file will be in build directory, -# # need that build directory to also appear in -# # compiler's include path -# # -# target_include_directories( -# ${target} PUBLIC -# $ # e.g. for #include "indentlog/scope.hpp" -# $ -# $ # e.g. for #include "Refcounted.hpp" in refcnt/src -# $ -# $ # e.g. for generated config.hpp file -# ) -# -# # ---------------------------------------------------------------- -# # make standard directories for std:: includes explicit -# # so that -# # (1) they appear in compile_commands.json. -# # (2) clangd (run from emacs lsp-mode) can find them -# # -# if(CMAKE_EXPORT_COMPILE_COMMANDS) -# set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) -# endif() -#endmacro() - -# ---------------------------------------------------------------- -# variable -# XO_ADDRESS_SANITIZE -# determines whether to enable address sanitizer for the XO project -# (see toplevel CMakeLists.txt) -# ---------------------------------------------------------------- -if(XO_ADDRESS_SANITIZE) - add_compile_options(-fsanitize=address) - add_link_options(-fsanitize=address) -endif() - -# XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF -set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) - -# XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON -# -# address sanitizer build complains about _FORTIFY_SOURCE redefines -# In file included from :460: -# :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] -# #define _FORTIFY_SOURCE 2 -# -set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) - -# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro -if(XO_ADDRESS_SANITIZE) - set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) -else() - set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) -endif() - -# ---------------------------------------------------------------- -# generally want all the errors+warnings! -# however: address sanitizer generates error on _FORTIFY_SOURCE -# -macro(xo_compile_options target) - target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) -endmacro() - +## variable +## XO_ADDRESS_SANITIZE +## determines whether to enable address sanitizer for the XO project +## (see toplevel CMakeLists.txt) ## ---------------------------------------------------------------- -## 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() +#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 ## -#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() +## address sanitizer build complains about _FORTIFY_SOURCE redefines +## In file included from :460: +## :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] +## #define _FORTIFY_SOURCE 2 +## +#set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) +# +## XO_COMPILE_OPTIONS: use these with xo_compile_options() macro +#if(XO_ADDRESS_SANITIZE) +# set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) +#else() +# set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) +#endif() # ## ---------------------------------------------------------------- -## use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers +## generally want all the errors+warnings! +## however: address sanitizer generates error on _FORTIFY_SOURCE ## -#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) +#macro(xo_compile_options target) +# target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) #endmacro() -# From 6d9eb677433a470f7141c41a09a8d64a25281f92 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:14:12 -0400 Subject: [PATCH 0080/2524] xo-cmake: bugfix: typos! --- cmake/xo_cxx.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index e435de55..1d1736fe 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -26,7 +26,7 @@ macro(xo_toplevel_compile_options) else() set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) endif() -endif() +endmacro() # ---------------------------------------------------------------- # use this in subdirs that compile c++ code @@ -68,7 +68,7 @@ endmacro() # needs to be preceded by call to xo_toplevel_compile_options() # macro(xo_compile_options target) - target_copmile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) + target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) endmacro() # ---------------------------------------------------------------- From fc1963424ce5d69e5e6f1887ee96979e8438db4a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:28:43 -0400 Subject: [PATCH 0081/2524] xo-cmake: + code-coverage.cmake --- cmake/code-coverage.cmake | 678 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 678 insertions(+) create mode 100644 cmake/code-coverage.cmake diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake new file mode 100644 index 00000000..b6b36064 --- /dev/null +++ b/cmake/code-coverage.cmake @@ -0,0 +1,678 @@ +# +# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# USAGE: To enable any code coverage instrumentation/targets, the single CMake +# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or +# on the command line. +# +# From this point, there are two primary methods for adding instrumentation to +# targets: +# +# 1 - A blanket instrumentation by calling `add_code_coverage()`, where +# all targets in that directory and all subdirectories are automatically +# instrumented. +# +# 2 - Per-target instrumentation by calling +# `target_code_coverage()`, where the target is given and thus only +# that target is instrumented. This applies to both libraries and executables. +# +# To add coverage targets, such as calling `make ccov` to generate the actual +# coverage information for perusal or consumption, call +# `target_code_coverage()` on an *executable* target. +# +# Example 1: All targets instrumented +# +# In this case, the coverage information reported will will be that of the +# `theLib` library target and `theExe` executable. +# +# 1a: Via global command +# +# ~~~ +# add_code_coverage() # Adds instrumentation to all targets +# +# add_library(theLib lib.cpp) +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target +# # (instrumentation already added via global anyways) +# # for generating code coverage reports. +# ~~~ +# +# 1b: Via target commands +# +# ~~~ +# add_library(theLib lib.cpp) +# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. +# ~~~ +# +# Example 2: Target instrumented, but with regex pattern of files to be excluded +# from report +# +# ~~~ +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ +# +# Example 3: Target added to the 'ccov' and 'ccov-all' targets +# +# ~~~ +# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. +# +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ + +# Options +option( + CODE_COVERAGE + "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" + OFF) + +# Programs +find_program(LLVM_COV_PATH llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) +# Hide behind the 'advanced' mode flag for GUI/ccmake +mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) + +# Variables +set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) +set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) + +# Common initialization/checks +if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) + set(CODE_COVERAGE_ADDED ON) + + # Common Targets + add_custom_target( + ccov-preprocessing + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} + DEPENDS ccov-clean) + + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + # Messages + message(STATUS "Building with llvm Code Coverage Tools") + + if(NOT LLVM_COV_PATH) + message(FATAL_ERROR "llvm-cov not found! Aborting.") + else() + # Version number checking for 'EXCLUDE' compatibility + execute_process(COMMAND ${LLVM_COV_PATH} --version + OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION + ${LLVM_COV_VERSION_CALL_OUTPUT}) + + if(LLVM_COV_VERSION VERSION_LESS "7.0.0") + message( + WARNING + "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" + ) + endif() + endif() + + # Targets + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + else() + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + endif() + + # Used to get the shared object file list before doing the main all- + # processing + add_custom_target( + ccov-libs + COMMAND ; + COMMENT "libs ready for coverage report.") + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + # Messages + message(STATUS "Building with lcov Code Coverage Tools") + + if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) + if(NOT ${upper_build_type} STREQUAL "DEBUG") + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + else() + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Targets + add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory + ${CMAKE_BINARY_DIR} --zerocounters) + + else() + message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") + endif() +endif() + +# Adds code coverage instrumentation to a library, or instrumentation/targets +# for an executable target. +# ~~~ +# EXECUTABLE ADDED TARGETS: +# GCOV/LCOV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# +# LLVM-COV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report. +# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. +# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. +# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. +# ccov-all-export : Exports the coverage report to a JSON file. +# +# Required: +# TARGET_NAME - Name of the target to generate code coverage for. +# Optional: +# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. +# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. +# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) +# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. +# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. +# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory +# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** +# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call +# ~~~ +function(target_code_coverage TARGET_NAME) + # Argument parsing + set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) + set(single_value_keywords COVERAGE_TARGET_NAME) + set(multi_value_keywords EXCLUDE OBJECTS ARGS) + cmake_parse_arguments( + target_code_coverage "${options}" "${single_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + # Set the visibility of target functions to PUBLIC, INTERFACE or default to + # PRIVATE. + if(target_code_coverage_PUBLIC) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY PUBLIC) + elseif(target_code_coverage_INTERFACE) + set(TARGET_VISIBILITY INTERFACE) + set(TARGET_LINK_VISIBILITY INTERFACE) + elseif(target_code_coverage_PLAIN) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY) + else() + set(TARGET_VISIBILITY PRIVATE) + set(TARGET_LINK_VISIBILITY PRIVATE) + endif() + + if(NOT target_code_coverage_COVERAGE_TARGET_NAME) + # If a specific name was given, use that instead. + set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) + endif() + + if(CODE_COVERAGE) + + # Add code coverage instrumentation to the target's linker command + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs + -ftest-coverage) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) + endif() + + # Targets + get_target_property(target_type ${TARGET_NAME} TYPE) + + # Add shared library to processing for 'all' targets + if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" >> + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + if(NOT TARGET ccov-libs) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-libs + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # If there are shared objects to also work with, generate the string to + # add them here + foreach(SO_TARGET ${target_code_coverage_OBJECTS}) + # Check to see if the target is a shared object + if(TARGET ${SO_TARGET}) + get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) + if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") + set(SO_OBJECTS ${SO_OBJECTS} -object=$) + endif() + endif() + endforeach() + + # Run the executable, generating raw profile data Make the run data + # available for further processing. Separated to allow Windows to run + # this target serially. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list + JOB_POOL ccov_serial_pool + DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) + + # Merge the generated profile data so llvm-cov can process it + add_custom_target( + ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_PROFDATA_PATH} merge -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Ignore regex only works on LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print out details of the coverage information to the command line + add_custom_target( + ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Print out a summary of the coverage information to the command line + add_custom_target( + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) + + # Run the executable, generating coverage information + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND $ ${target_code_coverage_ARGS} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + # Generate exclusion string for use + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + if(NOT ${target_code_coverage_EXTERNAL}) + set(EXTERNAL_OPTION --no-external) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + else() + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + endif() + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + + add_custom_command( + TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." + ) + + # AUTO + if(target_code_coverage_AUTO) + if(NOT TARGET ccov) + add_custom_target(ccov) + endif() + add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) + + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) + endif() + add_dependencies( + ccov-report + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # ALL + if(target_code_coverage_ALL) + if(NOT TARGET ccov-all-processing) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-all-processing + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + endif() +endfunction() + +# Adds code coverage instrumentation to all targets in the current directory and +# any subdirectories. To add coverage instrumentation to only specific targets, +# use `target_code_coverage`. +function(add_code_coverage) + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_compile_options(-fprofile-instr-generate -fcoverage-mapping) + add_link_options(-fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options(-fprofile-arcs -ftest-coverage) + link_libraries(gcov) + endif() + endif() +endfunction() + +# Adds the 'ccov-all' type targets that calls all targets added via +# `target_code_coverage` with the `ALL` parameter, but merges all the coverage +# data from them into a single large report instead of the numerous smaller +# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for +# use with coverage dashboards (e.g. codecov.io, coveralls). +# ~~~ +# Optional: +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! +# ~~~ +function(add_code_coverage_all_targets) + # Argument parsing + set(multi_value_keywords EXCLUDE) + cmake_parse_arguments(add_code_coverage_all_targets "" "" + "${multi_value_keywords}" ${ARGN}) + + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # Merge the profile data for all of the run executables + if(WIN32) + add_custom_target( + ccov-all-processing + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe + merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -sparse $$FILELIST) + else() + add_custom_target( + ccov-all-processing + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + endif() + + # Regex exclude only available for LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print summary of the code coverage information to the command line + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-all-export + COMMAND + ${LLVM_COV_PATH} export `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + + # Generate HTML output of all added targets for perusal + if(WIN32) + add_custom_target( + ccov-all + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") + + # Nothing required for gcov + add_custom_target(ccov-all-processing COMMAND ;) + + # Exclusion regex string creation + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + else() + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + endif() + + # Generates HTML output of all targets for perusal + add_custom_target( + ccov-all + COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} + DEPENDS ccov-all-capture) + + endif() + + add_custom_command( + TARGET ccov-all + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." + ) + endif() +endfunction() From 6ab05249030dcf074c67e8bb86144d7bfd5c5143 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:31:52 -0400 Subject: [PATCH 0082/2524] xo-cmake: bugfix: must install code-coverage.cmake --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b1480c47..c018ef2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,5 +13,6 @@ set(XO_PROJECT_NAME xo_macros) install( FILES "cmake/xo_cxx.cmake" + "cmake/code-coverage.cmake" DESTINATION share/cmake/${XO_PROJECT_NAME} ) From ef0b02f4cae31cd7f3e24f2afd6c126fca62e6d1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:32:29 -0400 Subject: [PATCH 0083/2524] reflect: use xo_macros code coverage --- CMakeLists.txt | 3 +-- cmake/cxx.cmake | 37 ------------------------------------- 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b0240d0..4ca908c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,7 @@ enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) include(xo_macros/xo_cxx) -include(cmake/cxx.cmake) -include(cmake/code-coverage.cmake) +include(xo_macros/code-coverage) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake index ede44e35..e69de29b 100644 --- a/cmake/cxx.cmake +++ b/cmake/cxx.cmake @@ -1,37 +0,0 @@ -## ---------------------------------------------------------------- -## variable -## XO_ADDRESS_SANITIZE -## determines whether to enable address sanitizer for the XO project -## (see toplevel CMakeLists.txt) -## ---------------------------------------------------------------- -#if(XO_ADDRESS_SANITIZE) -# add_compile_options(-fsanitize=address) -# add_link_options(-fsanitize=address) -#endif() -# -## XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF -#set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) -# -## XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON -## -## address sanitizer build complains about _FORTIFY_SOURCE redefines -## In file included from :460: -## :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] -## #define _FORTIFY_SOURCE 2 -## -#set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) -# -## XO_COMPILE_OPTIONS: use these with xo_compile_options() macro -#if(XO_ADDRESS_SANITIZE) -# set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) -#else() -# set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) -#endif() -# -## ---------------------------------------------------------------- -## generally want all the errors+warnings! -## however: address sanitizer generates error on _FORTIFY_SOURCE -## -#macro(xo_compile_options target) -# target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) -#endmacro() From e8a79ebb57967e8db79c2a0052018005e0ca45b2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:33:05 -0400 Subject: [PATCH 0084/2524] reflect: retire superseded .cmake files --- cmake/code-coverage.cmake | 678 -------------------------------------- cmake/cxx.cmake | 0 2 files changed, 678 deletions(-) delete mode 100644 cmake/code-coverage.cmake delete mode 100644 cmake/cxx.cmake diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake deleted file mode 100644 index b6b36064..00000000 --- a/cmake/code-coverage.cmake +++ /dev/null @@ -1,678 +0,0 @@ -# -# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - -# USAGE: To enable any code coverage instrumentation/targets, the single CMake -# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or -# on the command line. -# -# From this point, there are two primary methods for adding instrumentation to -# targets: -# -# 1 - A blanket instrumentation by calling `add_code_coverage()`, where -# all targets in that directory and all subdirectories are automatically -# instrumented. -# -# 2 - Per-target instrumentation by calling -# `target_code_coverage()`, where the target is given and thus only -# that target is instrumented. This applies to both libraries and executables. -# -# To add coverage targets, such as calling `make ccov` to generate the actual -# coverage information for perusal or consumption, call -# `target_code_coverage()` on an *executable* target. -# -# Example 1: All targets instrumented -# -# In this case, the coverage information reported will will be that of the -# `theLib` library target and `theExe` executable. -# -# 1a: Via global command -# -# ~~~ -# add_code_coverage() # Adds instrumentation to all targets -# -# add_library(theLib lib.cpp) -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target -# # (instrumentation already added via global anyways) -# # for generating code coverage reports. -# ~~~ -# -# 1b: Via target commands -# -# ~~~ -# add_library(theLib lib.cpp) -# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. -# ~~~ -# -# Example 2: Target instrumented, but with regex pattern of files to be excluded -# from report -# -# ~~~ -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ -# -# Example 3: Target added to the 'ccov' and 'ccov-all' targets -# -# ~~~ -# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. -# -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ - -# Options -option( - CODE_COVERAGE - "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" - OFF) - -# Programs -find_program(LLVM_COV_PATH llvm-cov) -find_program(LLVM_PROFDATA_PATH llvm-profdata) -find_program(LCOV_PATH lcov) -find_program(GENHTML_PATH genhtml) -# Hide behind the 'advanced' mode flag for GUI/ccmake -mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) - -# Variables -set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) -set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) - -# Common initialization/checks -if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) - set(CODE_COVERAGE_ADDED ON) - - # Common Targets - add_custom_target( - ccov-preprocessing - COMMAND ${CMAKE_COMMAND} -E make_directory - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} - DEPENDS ccov-clean) - - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - # Messages - message(STATUS "Building with llvm Code Coverage Tools") - - if(NOT LLVM_COV_PATH) - message(FATAL_ERROR "llvm-cov not found! Aborting.") - else() - # Version number checking for 'EXCLUDE' compatibility - execute_process(COMMAND ${LLVM_COV_PATH} --version - OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) - string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION - ${LLVM_COV_VERSION_CALL_OUTPUT}) - - if(LLVM_COV_VERSION VERSION_LESS "7.0.0") - message( - WARNING - "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" - ) - endif() - endif() - - # Targets - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - else() - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - endif() - - # Used to get the shared object file list before doing the main all- - # processing - add_custom_target( - ccov-libs - COMMAND ; - COMMENT "libs ready for coverage report.") - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - # Messages - message(STATUS "Building with lcov Code Coverage Tools") - - if(CMAKE_BUILD_TYPE) - string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) - if(NOT ${upper_build_type} STREQUAL "DEBUG") - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - else() - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - if(NOT LCOV_PATH) - message(FATAL_ERROR "lcov not found! Aborting...") - endif() - if(NOT GENHTML_PATH) - message(FATAL_ERROR "genhtml not found! Aborting...") - endif() - - # Targets - add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory - ${CMAKE_BINARY_DIR} --zerocounters) - - else() - message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") - endif() -endif() - -# Adds code coverage instrumentation to a library, or instrumentation/targets -# for an executable target. -# ~~~ -# EXECUTABLE ADDED TARGETS: -# GCOV/LCOV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# -# LLVM-COV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report. -# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. -# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. -# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. -# ccov-all-export : Exports the coverage report to a JSON file. -# -# Required: -# TARGET_NAME - Name of the target to generate code coverage for. -# Optional: -# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. -# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. -# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) -# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. -# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. -# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory -# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** -# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output -# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call -# ~~~ -function(target_code_coverage TARGET_NAME) - # Argument parsing - set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) - set(single_value_keywords COVERAGE_TARGET_NAME) - set(multi_value_keywords EXCLUDE OBJECTS ARGS) - cmake_parse_arguments( - target_code_coverage "${options}" "${single_value_keywords}" - "${multi_value_keywords}" ${ARGN}) - - # Set the visibility of target functions to PUBLIC, INTERFACE or default to - # PRIVATE. - if(target_code_coverage_PUBLIC) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY PUBLIC) - elseif(target_code_coverage_INTERFACE) - set(TARGET_VISIBILITY INTERFACE) - set(TARGET_LINK_VISIBILITY INTERFACE) - elseif(target_code_coverage_PLAIN) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY) - else() - set(TARGET_VISIBILITY PRIVATE) - set(TARGET_LINK_VISIBILITY PRIVATE) - endif() - - if(NOT target_code_coverage_COVERAGE_TARGET_NAME) - # If a specific name was given, use that instead. - set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) - endif() - - if(CODE_COVERAGE) - - # Add code coverage instrumentation to the target's linker command - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs - -ftest-coverage) - target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) - endif() - - # Targets - get_target_property(target_type ${TARGET_NAME} TYPE) - - # Add shared library to processing for 'all' targets - if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" >> - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - if(NOT TARGET ccov-libs) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-libs - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # For executables add targets to run and produce output - if(target_type STREQUAL "EXECUTABLE") - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # If there are shared objects to also work with, generate the string to - # add them here - foreach(SO_TARGET ${target_code_coverage_OBJECTS}) - # Check to see if the target is a shared object - if(TARGET ${SO_TARGET}) - get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) - if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") - set(SO_OBJECTS ${SO_OBJECTS} -object=$) - endif() - endif() - endforeach() - - # Run the executable, generating raw profile data Make the run data - # available for further processing. Separated to allow Windows to run - # this target serially. - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E env - LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw - $ ${target_code_coverage_ARGS} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" - ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND - ${CMAKE_COMMAND} -E echo - "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" - >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list - JOB_POOL ccov_serial_pool - DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) - - # Merge the generated profile data so llvm-cov can process it - add_custom_target( - ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_PROFDATA_PATH} merge -sparse - ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o - ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Ignore regex only works on LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print out details of the coverage information to the command line - add_custom_target( - ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Print out a summary of the coverage information to the command line - add_custom_target( - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} report $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} export $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO - "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" - ) - - # Run the executable, generating coverage information - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND $ ${target_code_coverage_ARGS} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - # Generate exclusion string for use - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - if(NOT ${target_code_coverage_EXTERNAL}) - set(EXTERNAL_OPTION --no-external) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - else() - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - endif() - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${GENHTML_PATH} -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - ${COVERAGE_INFO} - DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - - add_custom_command( - TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." - ) - - # AUTO - if(target_code_coverage_AUTO) - if(NOT TARGET ccov) - add_custom_target(ccov) - endif() - add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) - - if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID - MATCHES "GNU") - if(NOT TARGET ccov-report) - add_custom_target(ccov-report) - endif() - add_dependencies( - ccov-report - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # ALL - if(target_code_coverage_ALL) - if(NOT TARGET ccov-all-processing) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-all-processing - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - endif() -endfunction() - -# Adds code coverage instrumentation to all targets in the current directory and -# any subdirectories. To add coverage instrumentation to only specific targets, -# use `target_code_coverage`. -function(add_code_coverage) - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_compile_options(-fprofile-instr-generate -fcoverage-mapping) - add_link_options(-fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - add_compile_options(-fprofile-arcs -ftest-coverage) - link_libraries(gcov) - endif() - endif() -endfunction() - -# Adds the 'ccov-all' type targets that calls all targets added via -# `target_code_coverage` with the `ALL` parameter, but merges all the coverage -# data from them into a single large report instead of the numerous smaller -# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for -# use with coverage dashboards (e.g. codecov.io, coveralls). -# ~~~ -# Optional: -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! -# ~~~ -function(add_code_coverage_all_targets) - # Argument parsing - set(multi_value_keywords EXCLUDE) - cmake_parse_arguments(add_code_coverage_all_targets "" "" - "${multi_value_keywords}" ${ARGN}) - - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # Merge the profile data for all of the run executables - if(WIN32) - add_custom_target( - ccov-all-processing - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe - merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -sparse $$FILELIST) - else() - add_custom_target( - ccov-all-processing - COMMAND - ${LLVM_PROFDATA_PATH} merge -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) - endif() - - # Regex exclude only available for LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print summary of the code coverage information to the command line - if(WIN32) - add_custom_target( - ccov-all-report - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe - report $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all-report - COMMAND - ${LLVM_COV_PATH} report `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-all-export - COMMAND - ${LLVM_COV_PATH} export `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json - DEPENDS ccov-all-processing) - - # Generate HTML output of all added targets for perusal - if(WIN32) - add_custom_target( - ccov-all - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show - $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all - COMMAND - ${LLVM_COV_PATH} show `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") - - # Nothing required for gcov - add_custom_target(ccov-all-processing COMMAND ;) - - # Exclusion regex string creation - set(EXCLUDE_REGEX) - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - else() - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - endif() - - # Generates HTML output of all targets for perusal - add_custom_target( - ccov-all - COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} - DEPENDS ccov-all-capture) - - endif() - - add_custom_command( - TARGET ccov-all - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." - ) - endif() -endfunction() diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake deleted file mode 100644 index e69de29b..00000000 From 33f96604ee9052f540fd428ee9c34a22d4d2528e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:38:01 -0400 Subject: [PATCH 0085/2524] build: cosmetic: remove dead comments --- CMakeLists.txt | 48 -------------------------------------- src/reflect/CMakeLists.txt | 24 ------------------- 2 files changed, 72 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ca908c6..407d67c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,6 @@ if(NOT CMAKE_INSTALL_RPATH) set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") endif() - # early find_package() experiment find_package(indentlog CONFIG REQUIRED) find_package(refcnt CONFIG REQUIRED) @@ -66,54 +65,7 @@ add_subdirectory(utest) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -## ---------------------------------------------------------------- -## 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 xo_install_include_tree() -#install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/reflect/ DESTINATION include/reflect) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index bf709478..0b5c710e 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -22,32 +22,8 @@ xo_install_library2(${SELF_LIBRARY_NAME}) # ---------------------------------------------------------------- # dependencies: logutil, ... -#target_include_directories( -# ${SELF_LIBRARY_NAME} PUBLIC -# $ -# $ -#) - -#xo_refcnt_dependency(${SELF_LIBRARY_NAME}) -#xo_indentlog_dependency(${SELF_LIBRARY_NAME}) - -#add_dependencies(${SELF_LIBRARY_NAME} indentlog) -# note: can't use find_package() here, -# because find_package() needs to run successfully before -# dependency gets installed. xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) - -#target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC indentlog) - xo_internal_dependency(${SELF_LIBRARY_NAME} refcnt) -#find_package(refcnt CONFIG REQUIRED) -##add_dependencies(${SELF_LIBRARY_NAME} refcnt) -#target_include_directories( -# ${SELF_LIBRARY_NAME} PUBLIC -# $ -# $ -#) -#target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) # ---------------------------------------------------------------- # 3rd party dependency: boost: From b75e3b8fb2d6d5cb755a3b3682a96fbae370ca2e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:38:15 -0400 Subject: [PATCH 0086/2524] reflect: build: utest streamlining --- utest/CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 7eae2825..2e1ba51f 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -20,14 +20,15 @@ target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) # 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}) +xo_include_options2(${SELF_EXECUTABLE_NAME}) +#target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC +# ${PROJECT_SOURCE_DIR} +# ${PROJECT_BINARY_DIR}) # ---------------------------------------------------------------- # internal dependencies: logutil, ... -target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC reflect) +xo_internal_dependency(${SELF_EXECUTABLE_NAME} reflect) # ---------------------------------------------------------------- # 3rd part dependency: catch2: From 0c9d13b7eb0a915604a44ea417552f243e27f5f0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:54:26 -0400 Subject: [PATCH 0087/2524] xo-cmake: + xo_self_dependency() --- cmake/xo_cxx.cmake | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 1d1736fe..018f75fe 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -146,3 +146,14 @@ macro(xo_internal_dependency target dep) find_package(${dep} CONFIG REQUIRED) target_link_libraries(${target} PUBLIC ${dep}) endmacro() + +# dependency on target provided from this codebase. +# +# 1. don't need find_package() in this case, since details of dep targets +# must be known to cmake for it to build them. +# 2. in any case, can't use find_package() when cmake runs, +# because supporting .cmake files haven't been generated yet +# +macro(xo_self_dependency target dep) + target_link_libraries(${target} PUBLIC ${dep}) +endmacro() From 25712169ee585da4e61bbc78f4d2b0318e28b149 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 16:54:48 -0400 Subject: [PATCH 0088/2524] reflect: build: streamline --- CMakeLists.txt | 4 ++-- utest/CMakeLists.txt | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 407d67c6..ec69e9c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,8 +51,8 @@ if(NOT CMAKE_INSTALL_RPATH) endif() # early find_package() experiment -find_package(indentlog CONFIG REQUIRED) -find_package(refcnt CONFIG REQUIRED) +#find_package(indentlog CONFIG REQUIRED) +#find_package(refcnt CONFIG REQUIRED) # ---------------------------------------------------------------- # sources diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 2e1ba51f..75f16ee5 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -28,10 +28,11 @@ xo_include_options2(${SELF_EXECUTABLE_NAME}) # ---------------------------------------------------------------- # internal dependencies: logutil, ... -xo_internal_dependency(${SELF_EXECUTABLE_NAME} reflect) +# this won't work. when cmake runs, the Config files for reflect haven't been created yet. +xo_self_dependency(${SELF_EXECUTABLE_NAME} reflect) # ---------------------------------------------------------------- -# 3rd part dependency: catch2: +# 3rd party dependency: catch2: find_package(Catch2 2 REQUIRED) From a35770dc78d033659230449b823d743c36d7d9f2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:07:49 -0400 Subject: [PATCH 0089/2524] refcnt: build: streamline, using xo-cmake macros --- CMakeLists.txt | 38 +-- cmake/code-coverage.cmake | 678 -------------------------------------- src/CMakeLists.txt | 3 +- utest/CMakeLists.txt | 5 +- 4 files changed, 6 insertions(+), 718 deletions(-) delete mode 100644 cmake/code-coverage.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f7971f6..54ec054d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,8 @@ enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) include(xo_macros/xo_cxx) -include(cmake/cxx.cmake) -include(cmake/code-coverage.cmake) +#include(cmake/cxx.cmake) +include(xo_macros/code-coverage) # ---------------------------------------------------------------- # unit test setup @@ -55,43 +55,9 @@ add_subdirectory(utest) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -#set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") -#set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") -# -#include(CMakePackageConfigHelpers) -#write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" -# VERSION 0.1 -# COMPATIBILITY AnyNewerVersion -#) -# -##install( -## TARGETS ${XO_PROJECT_NAME} -## EXPORT ${XO_PROJECT_NAME}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 -## ) -# -#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} -# ) -# -#install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) -#install( -# FILES -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" -# DESTINATION lib/cmake/${XO_PROJECT_NAME}) - # ---------------------------------------------------------------- # install .hpp xo_install_include_tree() -#install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) - # end CMakeLists.txt diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake deleted file mode 100644 index b6b36064..00000000 --- a/cmake/code-coverage.cmake +++ /dev/null @@ -1,678 +0,0 @@ -# -# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - -# USAGE: To enable any code coverage instrumentation/targets, the single CMake -# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or -# on the command line. -# -# From this point, there are two primary methods for adding instrumentation to -# targets: -# -# 1 - A blanket instrumentation by calling `add_code_coverage()`, where -# all targets in that directory and all subdirectories are automatically -# instrumented. -# -# 2 - Per-target instrumentation by calling -# `target_code_coverage()`, where the target is given and thus only -# that target is instrumented. This applies to both libraries and executables. -# -# To add coverage targets, such as calling `make ccov` to generate the actual -# coverage information for perusal or consumption, call -# `target_code_coverage()` on an *executable* target. -# -# Example 1: All targets instrumented -# -# In this case, the coverage information reported will will be that of the -# `theLib` library target and `theExe` executable. -# -# 1a: Via global command -# -# ~~~ -# add_code_coverage() # Adds instrumentation to all targets -# -# add_library(theLib lib.cpp) -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target -# # (instrumentation already added via global anyways) -# # for generating code coverage reports. -# ~~~ -# -# 1b: Via target commands -# -# ~~~ -# add_library(theLib lib.cpp) -# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. -# ~~~ -# -# Example 2: Target instrumented, but with regex pattern of files to be excluded -# from report -# -# ~~~ -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ -# -# Example 3: Target added to the 'ccov' and 'ccov-all' targets -# -# ~~~ -# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. -# -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ - -# Options -option( - CODE_COVERAGE - "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" - OFF) - -# Programs -find_program(LLVM_COV_PATH llvm-cov) -find_program(LLVM_PROFDATA_PATH llvm-profdata) -find_program(LCOV_PATH lcov) -find_program(GENHTML_PATH genhtml) -# Hide behind the 'advanced' mode flag for GUI/ccmake -mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) - -# Variables -set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) -set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) - -# Common initialization/checks -if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) - set(CODE_COVERAGE_ADDED ON) - - # Common Targets - add_custom_target( - ccov-preprocessing - COMMAND ${CMAKE_COMMAND} -E make_directory - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} - DEPENDS ccov-clean) - - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - # Messages - message(STATUS "Building with llvm Code Coverage Tools") - - if(NOT LLVM_COV_PATH) - message(FATAL_ERROR "llvm-cov not found! Aborting.") - else() - # Version number checking for 'EXCLUDE' compatibility - execute_process(COMMAND ${LLVM_COV_PATH} --version - OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) - string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION - ${LLVM_COV_VERSION_CALL_OUTPUT}) - - if(LLVM_COV_VERSION VERSION_LESS "7.0.0") - message( - WARNING - "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" - ) - endif() - endif() - - # Targets - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - else() - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - endif() - - # Used to get the shared object file list before doing the main all- - # processing - add_custom_target( - ccov-libs - COMMAND ; - COMMENT "libs ready for coverage report.") - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - # Messages - message(STATUS "Building with lcov Code Coverage Tools") - - if(CMAKE_BUILD_TYPE) - string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) - if(NOT ${upper_build_type} STREQUAL "DEBUG") - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - else() - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - if(NOT LCOV_PATH) - message(FATAL_ERROR "lcov not found! Aborting...") - endif() - if(NOT GENHTML_PATH) - message(FATAL_ERROR "genhtml not found! Aborting...") - endif() - - # Targets - add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory - ${CMAKE_BINARY_DIR} --zerocounters) - - else() - message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") - endif() -endif() - -# Adds code coverage instrumentation to a library, or instrumentation/targets -# for an executable target. -# ~~~ -# EXECUTABLE ADDED TARGETS: -# GCOV/LCOV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# -# LLVM-COV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report. -# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. -# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. -# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. -# ccov-all-export : Exports the coverage report to a JSON file. -# -# Required: -# TARGET_NAME - Name of the target to generate code coverage for. -# Optional: -# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. -# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. -# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) -# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. -# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. -# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory -# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** -# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output -# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call -# ~~~ -function(target_code_coverage TARGET_NAME) - # Argument parsing - set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) - set(single_value_keywords COVERAGE_TARGET_NAME) - set(multi_value_keywords EXCLUDE OBJECTS ARGS) - cmake_parse_arguments( - target_code_coverage "${options}" "${single_value_keywords}" - "${multi_value_keywords}" ${ARGN}) - - # Set the visibility of target functions to PUBLIC, INTERFACE or default to - # PRIVATE. - if(target_code_coverage_PUBLIC) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY PUBLIC) - elseif(target_code_coverage_INTERFACE) - set(TARGET_VISIBILITY INTERFACE) - set(TARGET_LINK_VISIBILITY INTERFACE) - elseif(target_code_coverage_PLAIN) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY) - else() - set(TARGET_VISIBILITY PRIVATE) - set(TARGET_LINK_VISIBILITY PRIVATE) - endif() - - if(NOT target_code_coverage_COVERAGE_TARGET_NAME) - # If a specific name was given, use that instead. - set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) - endif() - - if(CODE_COVERAGE) - - # Add code coverage instrumentation to the target's linker command - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs - -ftest-coverage) - target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) - endif() - - # Targets - get_target_property(target_type ${TARGET_NAME} TYPE) - - # Add shared library to processing for 'all' targets - if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" >> - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - if(NOT TARGET ccov-libs) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-libs - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # For executables add targets to run and produce output - if(target_type STREQUAL "EXECUTABLE") - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # If there are shared objects to also work with, generate the string to - # add them here - foreach(SO_TARGET ${target_code_coverage_OBJECTS}) - # Check to see if the target is a shared object - if(TARGET ${SO_TARGET}) - get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) - if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") - set(SO_OBJECTS ${SO_OBJECTS} -object=$) - endif() - endif() - endforeach() - - # Run the executable, generating raw profile data Make the run data - # available for further processing. Separated to allow Windows to run - # this target serially. - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E env - LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw - $ ${target_code_coverage_ARGS} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" - ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND - ${CMAKE_COMMAND} -E echo - "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" - >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list - JOB_POOL ccov_serial_pool - DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) - - # Merge the generated profile data so llvm-cov can process it - add_custom_target( - ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_PROFDATA_PATH} merge -sparse - ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o - ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Ignore regex only works on LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print out details of the coverage information to the command line - add_custom_target( - ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Print out a summary of the coverage information to the command line - add_custom_target( - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} report $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} export $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO - "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" - ) - - # Run the executable, generating coverage information - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND $ ${target_code_coverage_ARGS} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - # Generate exclusion string for use - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - if(NOT ${target_code_coverage_EXTERNAL}) - set(EXTERNAL_OPTION --no-external) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - else() - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - endif() - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${GENHTML_PATH} -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - ${COVERAGE_INFO} - DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - - add_custom_command( - TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." - ) - - # AUTO - if(target_code_coverage_AUTO) - if(NOT TARGET ccov) - add_custom_target(ccov) - endif() - add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) - - if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID - MATCHES "GNU") - if(NOT TARGET ccov-report) - add_custom_target(ccov-report) - endif() - add_dependencies( - ccov-report - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # ALL - if(target_code_coverage_ALL) - if(NOT TARGET ccov-all-processing) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-all-processing - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - endif() -endfunction() - -# Adds code coverage instrumentation to all targets in the current directory and -# any subdirectories. To add coverage instrumentation to only specific targets, -# use `target_code_coverage`. -function(add_code_coverage) - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_compile_options(-fprofile-instr-generate -fcoverage-mapping) - add_link_options(-fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - add_compile_options(-fprofile-arcs -ftest-coverage) - link_libraries(gcov) - endif() - endif() -endfunction() - -# Adds the 'ccov-all' type targets that calls all targets added via -# `target_code_coverage` with the `ALL` parameter, but merges all the coverage -# data from them into a single large report instead of the numerous smaller -# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for -# use with coverage dashboards (e.g. codecov.io, coveralls). -# ~~~ -# Optional: -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! -# ~~~ -function(add_code_coverage_all_targets) - # Argument parsing - set(multi_value_keywords EXCLUDE) - cmake_parse_arguments(add_code_coverage_all_targets "" "" - "${multi_value_keywords}" ${ARGN}) - - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # Merge the profile data for all of the run executables - if(WIN32) - add_custom_target( - ccov-all-processing - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe - merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -sparse $$FILELIST) - else() - add_custom_target( - ccov-all-processing - COMMAND - ${LLVM_PROFDATA_PATH} merge -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) - endif() - - # Regex exclude only available for LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print summary of the code coverage information to the command line - if(WIN32) - add_custom_target( - ccov-all-report - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe - report $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all-report - COMMAND - ${LLVM_COV_PATH} report `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-all-export - COMMAND - ${LLVM_COV_PATH} export `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json - DEPENDS ccov-all-processing) - - # Generate HTML output of all added targets for perusal - if(WIN32) - add_custom_target( - ccov-all - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show - $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all - COMMAND - ${LLVM_COV_PATH} show `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") - - # Nothing required for gcov - add_custom_target(ccov-all-processing COMMAND ;) - - # Exclusion regex string creation - set(EXCLUDE_REGEX) - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - else() - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - endif() - - # Generates HTML output of all targets for perusal - add_custom_target( - ccov-all - COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} - DEPENDS ccov-all-capture) - - endif() - - add_custom_command( - TARGET ccov-all - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." - ) - endif() -endfunction() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f58170e8..0a60fbea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,8 +8,7 @@ set_target_properties(${SELF_LIBRARY_NAME} SOVERSION 1) xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) -#xo_indentlog_dependency(${SELF_LIBRARY_NAME}) xo_include_options2(${SELF_LIBRARY_NAME}) xo_compile_options(${SELF_LIBRARY_NAME}) -xo_install_library(${SELF_LIBRARY_NAME}) +xo_install_library2(${SELF_LIBRARY_NAME}) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 74706592..1f5ced17 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -5,7 +5,7 @@ set(SELF_EXECUTABLE_NAME utest.refcnt) # These tests can use the Catch2-provided main set(SELF_SOURCE_FILES intrusive_ptr.test.cpp refcnt_utest_main.cpp) add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) -xo_include_options(${SELF_EXECUTABLE_NAME}) +xo_include_options2(${SELF_EXECUTABLE_NAME}) add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) @@ -30,7 +30,8 @@ target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC # ---------------------------------------------------------------- # internal dependencies: refcnt, ... -target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC refcnt) +xo_self_dependency(${SELF_EXECUTABLE_NAME} refcnt) +#target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC refcnt) # ---------------------------------------------------------------- # 3rd part dependency: catch2: From 31d8ce44def547b27e8a9c0eec52442899a30a06 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:20:26 -0400 Subject: [PATCH 0090/2524] xo-cmake: + xo_include_headeronly_options2() --- cmake/xo_cxx.cmake | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 018f75fe..1e5d8897 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -29,7 +29,8 @@ macro(xo_toplevel_compile_options) endmacro() # ---------------------------------------------------------------- -# use this in subdirs that compile c++ code +# use this in subdirs that compile c++ code. +# do not use for header-only subsystems; see xo_include_headeronly_options2() # macro(xo_include_options2 target) # ---------------------------------------------------------------- @@ -62,6 +63,37 @@ macro(xo_include_options2 target) endif() endmacro() +macro(xo_include_headeronly_options2 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} INTERFACE + $ # e.g. for #include "indentlog/scope.hpp" + $ + $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ + $ # e.g. for generated .hpp files + ) + + # ---------------------------------------------------------------- + # 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() + # ---------------------------------------------------------------- # # Require: From bc4335af82b838378780140197a0c22e1e0b0531 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:21:17 -0400 Subject: [PATCH 0091/2524] subsys: build: streamline using xo-cmake macros --- CMakeLists.txt | 40 ++++++++-------------------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c0b9c1e..61140a28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(subsys VERSION 0.1) enable_language(CXX) include (xo_macros/xo_cxx) -include(cmake/cxx.cmake) +#include(cmake/cxx.cmake) include(cmake/code-coverage.cmake) enable_testing() @@ -29,6 +29,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") +xo_toplevel_compile_options() + # ---------------------------------------------------------------- # - author's convenience: default install prefix to /home/$USER/local # - otherwise use -DCMAKE_INSTALL_PREFIX=/path/to/somewhere @@ -54,10 +56,11 @@ endif() # installing header-only library add_library(subsys INTERFACE) -target_include_directories(subsys INTERFACE - $ - $ -) +xo_include_headeronly_options2(subsys) +#target_include_directories(subsys INTERFACE +# $ +# $ +#) xo_install_library2(subsys) # ---------------------------------------------------------------- @@ -65,36 +68,9 @@ xo_install_library2(subsys) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -## ---------------------------------------------------------------- -## cmake export -## (so this library works with cmake's find_package()) -# -#set(XO_PROJECT_CONFIG_VERSION "${XO_PROJECT_NAME}ConfigVersion.cmake") -#set(XO_PROJECT_CONFIG "${XO_PROJECT_NAME}Config.cmake") -# -#include(CMakePackageConfigHelpers) -#write_basic_package_version_file("${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" -# VERSION 0.1 -# COMPATIBILITY AnyNewerVersion -#) -# -#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} -# ) -# -#install(EXPORT ${XO_PROJECT_NAME}Targets DESTINATION lib/cmake/${XO_PROJECT_NAME}) -#install( -# FILES -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG_VERSION}" -# "${PROJECT_BINARY_DIR}/${XO_PROJECT_CONFIG}" -# DESTINATION lib/cmake/${XO_PROJECT_NAME}) - # ---------------------------------------------------------------- # install .hpp xo_install_include_tree() -#install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) # end CMakeLists.txt From a2e3bda4ff1549d8a826b2971fb62113c34c6946 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:21:51 -0400 Subject: [PATCH 0092/2524] refcnt: build: bugfix: + xo_toplevel_compile_options() --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 54ec054d..3c5a6fc6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") +xo_toplevel_compile_options() + if(NOT CMAKE_INSTALL_RPATH) set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") endif() From a8ec3021ebfadbd87a1427551d21e431bc358aa6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:33:43 -0400 Subject: [PATCH 0093/2524] subsys: build tidy --- CMakeLists.txt | 5 +- cmake/code-coverage.cmake | 678 -------------------------------------- cmake/cxx.cmake | 96 ------ 3 files changed, 2 insertions(+), 777 deletions(-) delete mode 100644 cmake/code-coverage.cmake delete mode 100644 cmake/cxx.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 61140a28..126d305b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,9 +3,8 @@ cmake_minimum_required(VERSION 3.10) project(subsys VERSION 0.1) enable_language(CXX) -include (xo_macros/xo_cxx) -#include(cmake/cxx.cmake) -include(cmake/code-coverage.cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) enable_testing() # activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON) diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake deleted file mode 100644 index b6b36064..00000000 --- a/cmake/code-coverage.cmake +++ /dev/null @@ -1,678 +0,0 @@ -# -# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - -# USAGE: To enable any code coverage instrumentation/targets, the single CMake -# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or -# on the command line. -# -# From this point, there are two primary methods for adding instrumentation to -# targets: -# -# 1 - A blanket instrumentation by calling `add_code_coverage()`, where -# all targets in that directory and all subdirectories are automatically -# instrumented. -# -# 2 - Per-target instrumentation by calling -# `target_code_coverage()`, where the target is given and thus only -# that target is instrumented. This applies to both libraries and executables. -# -# To add coverage targets, such as calling `make ccov` to generate the actual -# coverage information for perusal or consumption, call -# `target_code_coverage()` on an *executable* target. -# -# Example 1: All targets instrumented -# -# In this case, the coverage information reported will will be that of the -# `theLib` library target and `theExe` executable. -# -# 1a: Via global command -# -# ~~~ -# add_code_coverage() # Adds instrumentation to all targets -# -# add_library(theLib lib.cpp) -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target -# # (instrumentation already added via global anyways) -# # for generating code coverage reports. -# ~~~ -# -# 1b: Via target commands -# -# ~~~ -# add_library(theLib lib.cpp) -# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. -# ~~~ -# -# Example 2: Target instrumented, but with regex pattern of files to be excluded -# from report -# -# ~~~ -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ -# -# Example 3: Target added to the 'ccov' and 'ccov-all' targets -# -# ~~~ -# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. -# -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ - -# Options -option( - CODE_COVERAGE - "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" - OFF) - -# Programs -find_program(LLVM_COV_PATH llvm-cov) -find_program(LLVM_PROFDATA_PATH llvm-profdata) -find_program(LCOV_PATH lcov) -find_program(GENHTML_PATH genhtml) -# Hide behind the 'advanced' mode flag for GUI/ccmake -mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) - -# Variables -set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) -set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) - -# Common initialization/checks -if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) - set(CODE_COVERAGE_ADDED ON) - - # Common Targets - add_custom_target( - ccov-preprocessing - COMMAND ${CMAKE_COMMAND} -E make_directory - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} - DEPENDS ccov-clean) - - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - # Messages - message(STATUS "Building with llvm Code Coverage Tools") - - if(NOT LLVM_COV_PATH) - message(FATAL_ERROR "llvm-cov not found! Aborting.") - else() - # Version number checking for 'EXCLUDE' compatibility - execute_process(COMMAND ${LLVM_COV_PATH} --version - OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) - string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION - ${LLVM_COV_VERSION_CALL_OUTPUT}) - - if(LLVM_COV_VERSION VERSION_LESS "7.0.0") - message( - WARNING - "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" - ) - endif() - endif() - - # Targets - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - else() - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - endif() - - # Used to get the shared object file list before doing the main all- - # processing - add_custom_target( - ccov-libs - COMMAND ; - COMMENT "libs ready for coverage report.") - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - # Messages - message(STATUS "Building with lcov Code Coverage Tools") - - if(CMAKE_BUILD_TYPE) - string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) - if(NOT ${upper_build_type} STREQUAL "DEBUG") - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - else() - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - if(NOT LCOV_PATH) - message(FATAL_ERROR "lcov not found! Aborting...") - endif() - if(NOT GENHTML_PATH) - message(FATAL_ERROR "genhtml not found! Aborting...") - endif() - - # Targets - add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory - ${CMAKE_BINARY_DIR} --zerocounters) - - else() - message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") - endif() -endif() - -# Adds code coverage instrumentation to a library, or instrumentation/targets -# for an executable target. -# ~~~ -# EXECUTABLE ADDED TARGETS: -# GCOV/LCOV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# -# LLVM-COV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report. -# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. -# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. -# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. -# ccov-all-export : Exports the coverage report to a JSON file. -# -# Required: -# TARGET_NAME - Name of the target to generate code coverage for. -# Optional: -# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. -# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. -# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) -# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. -# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. -# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory -# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** -# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output -# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call -# ~~~ -function(target_code_coverage TARGET_NAME) - # Argument parsing - set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) - set(single_value_keywords COVERAGE_TARGET_NAME) - set(multi_value_keywords EXCLUDE OBJECTS ARGS) - cmake_parse_arguments( - target_code_coverage "${options}" "${single_value_keywords}" - "${multi_value_keywords}" ${ARGN}) - - # Set the visibility of target functions to PUBLIC, INTERFACE or default to - # PRIVATE. - if(target_code_coverage_PUBLIC) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY PUBLIC) - elseif(target_code_coverage_INTERFACE) - set(TARGET_VISIBILITY INTERFACE) - set(TARGET_LINK_VISIBILITY INTERFACE) - elseif(target_code_coverage_PLAIN) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY) - else() - set(TARGET_VISIBILITY PRIVATE) - set(TARGET_LINK_VISIBILITY PRIVATE) - endif() - - if(NOT target_code_coverage_COVERAGE_TARGET_NAME) - # If a specific name was given, use that instead. - set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) - endif() - - if(CODE_COVERAGE) - - # Add code coverage instrumentation to the target's linker command - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs - -ftest-coverage) - target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) - endif() - - # Targets - get_target_property(target_type ${TARGET_NAME} TYPE) - - # Add shared library to processing for 'all' targets - if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" >> - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - if(NOT TARGET ccov-libs) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-libs - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # For executables add targets to run and produce output - if(target_type STREQUAL "EXECUTABLE") - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # If there are shared objects to also work with, generate the string to - # add them here - foreach(SO_TARGET ${target_code_coverage_OBJECTS}) - # Check to see if the target is a shared object - if(TARGET ${SO_TARGET}) - get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) - if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") - set(SO_OBJECTS ${SO_OBJECTS} -object=$) - endif() - endif() - endforeach() - - # Run the executable, generating raw profile data Make the run data - # available for further processing. Separated to allow Windows to run - # this target serially. - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E env - LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw - $ ${target_code_coverage_ARGS} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" - ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND - ${CMAKE_COMMAND} -E echo - "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" - >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list - JOB_POOL ccov_serial_pool - DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) - - # Merge the generated profile data so llvm-cov can process it - add_custom_target( - ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_PROFDATA_PATH} merge -sparse - ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o - ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Ignore regex only works on LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print out details of the coverage information to the command line - add_custom_target( - ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Print out a summary of the coverage information to the command line - add_custom_target( - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} report $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} export $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO - "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" - ) - - # Run the executable, generating coverage information - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND $ ${target_code_coverage_ARGS} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - # Generate exclusion string for use - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - if(NOT ${target_code_coverage_EXTERNAL}) - set(EXTERNAL_OPTION --no-external) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - else() - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - endif() - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${GENHTML_PATH} -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - ${COVERAGE_INFO} - DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - - add_custom_command( - TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." - ) - - # AUTO - if(target_code_coverage_AUTO) - if(NOT TARGET ccov) - add_custom_target(ccov) - endif() - add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) - - if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID - MATCHES "GNU") - if(NOT TARGET ccov-report) - add_custom_target(ccov-report) - endif() - add_dependencies( - ccov-report - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # ALL - if(target_code_coverage_ALL) - if(NOT TARGET ccov-all-processing) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-all-processing - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - endif() -endfunction() - -# Adds code coverage instrumentation to all targets in the current directory and -# any subdirectories. To add coverage instrumentation to only specific targets, -# use `target_code_coverage`. -function(add_code_coverage) - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_compile_options(-fprofile-instr-generate -fcoverage-mapping) - add_link_options(-fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - add_compile_options(-fprofile-arcs -ftest-coverage) - link_libraries(gcov) - endif() - endif() -endfunction() - -# Adds the 'ccov-all' type targets that calls all targets added via -# `target_code_coverage` with the `ALL` parameter, but merges all the coverage -# data from them into a single large report instead of the numerous smaller -# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for -# use with coverage dashboards (e.g. codecov.io, coveralls). -# ~~~ -# Optional: -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! -# ~~~ -function(add_code_coverage_all_targets) - # Argument parsing - set(multi_value_keywords EXCLUDE) - cmake_parse_arguments(add_code_coverage_all_targets "" "" - "${multi_value_keywords}" ${ARGN}) - - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # Merge the profile data for all of the run executables - if(WIN32) - add_custom_target( - ccov-all-processing - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe - merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -sparse $$FILELIST) - else() - add_custom_target( - ccov-all-processing - COMMAND - ${LLVM_PROFDATA_PATH} merge -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) - endif() - - # Regex exclude only available for LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print summary of the code coverage information to the command line - if(WIN32) - add_custom_target( - ccov-all-report - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe - report $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all-report - COMMAND - ${LLVM_COV_PATH} report `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-all-export - COMMAND - ${LLVM_COV_PATH} export `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json - DEPENDS ccov-all-processing) - - # Generate HTML output of all added targets for perusal - if(WIN32) - add_custom_target( - ccov-all - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show - $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all - COMMAND - ${LLVM_COV_PATH} show `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") - - # Nothing required for gcov - add_custom_target(ccov-all-processing COMMAND ;) - - # Exclusion regex string creation - set(EXCLUDE_REGEX) - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - else() - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - endif() - - # Generates HTML output of all targets for perusal - add_custom_target( - ccov-all - COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} - DEPENDS ccov-all-capture) - - endif() - - add_custom_command( - TARGET ccov-all - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." - ) - endif() -endfunction() diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake deleted file mode 100644 index 7fa32372..00000000 --- a/cmake/cxx.cmake +++ /dev/null @@ -1,96 +0,0 @@ -# ---------------------------------------------------------------- -# use this in subdirs that compile c++ code -# -macro(xo_include_options target) - # ---------------------------------------------------------------- - # PROJECT_SOURCE_DIR: - # so we can for example write - # #include "ordinaltree/foo.hpp" - # from anywhere in the project - # PROJECT_BINARY_DIR: - # since generated version file will be in build directory, - # need that build directory to also appear in - # compiler's include path - # - target_include_directories( - ${target} PUBLIC - $ # e.g. for #include "indentlog/scope.hpp" - $ - $ # e.g. for #include "Refcounted.hpp" in refcnt/src - $ - $ # e.g. for generated config.hpp file - ) - - # ---------------------------------------------------------------- - # make standard directories for std:: includes explicit - # so that - # (1) they appear in compile_commands.json. - # (2) clangd (run from emacs lsp-mode) can find them - # - if(CMAKE_EXPORT_COMPILE_COMMANDS) - set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) - endif() -endmacro() - -# ---------------------------------------------------------------- -# variable -# XO_ADDRESS_SANITIZE -# determines whether to enable address sanitizer for the XO project -# (see toplevel CMakeLists.txt) -# ---------------------------------------------------------------- -if(XO_ADDRESS_SANITIZE) - add_compile_options(-fsanitize=address) - add_link_options(-fsanitize=address) -endif() - -# XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF -set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) - -# XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON -# -# address sanitizer build complains about _FORTIFY_SOURCE redefines -# In file included from :460: -# :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] -# #define _FORTIFY_SOURCE 2 -# -set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) - -# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro -if(XO_ADDRESS_SANITIZE) - set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) -else() - set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) -endif() - -# ---------------------------------------------------------------- -# generally want all the errors+warnings! -# however: address sanitizer generates error on _FORTIFY_SOURCE -# -macro(xo_compile_options target) - target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) -endmacro() - -# ---------------------------------------------------------------- -# use this for a subdir that builds a library -# -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_indentlogg_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() From 6d09431d407206ea82609dd7f37630219849db66 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:34:19 -0400 Subject: [PATCH 0094/2524] refcnt: build tidy --- CMakeLists.txt | 1 - cmake/cxx.cmake | 97 ------------------------------------------------- 2 files changed, 98 deletions(-) delete mode 100644 cmake/cxx.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c5a6fc6..bc8e2ef1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,6 @@ enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) include(xo_macros/xo_cxx) -#include(cmake/cxx.cmake) include(xo_macros/code-coverage) # ---------------------------------------------------------------- diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake deleted file mode 100644 index fd9aed25..00000000 --- a/cmake/cxx.cmake +++ /dev/null @@ -1,97 +0,0 @@ -# ---------------------------------------------------------------- -# use this in subdirs that compile c++ code -# -macro(xo_include_options target) - # ---------------------------------------------------------------- - # PROJECT_SOURCE_DIR: - # so we can for example write - # #include "ordinaltree/foo.hpp" - # from anywhere in the project - # PROJECT_BINARY_DIR: - # since generated version file will be in build directory, - # need that build directory to also appear in - # compiler's include path - # - target_include_directories( - ${target} PUBLIC - $ # e.g. for #include "indentlog/scope.hpp" - $ - $ # e.g. for #include "Refcounted.hpp" in refcnt/src - $ - $ # e.g. for generated config.hpp file - ) - - # ---------------------------------------------------------------- - # make standard directories for std:: includes explicit - # so that - # (1) they appear in compile_commands.json. - # (2) clangd (run from emacs lsp-mode) can find them - # - if(CMAKE_EXPORT_COMPILE_COMMANDS) - set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) - endif() -endmacro() - -# ---------------------------------------------------------------- -# variable -# XO_ADDRESS_SANITIZE -# determines whether to enable address sanitizer for the XO project -# (see toplevel CMakeLists.txt) -# ---------------------------------------------------------------- -if(XO_ADDRESS_SANITIZE) - add_compile_options(-fsanitize=address) - add_link_options(-fsanitize=address) -endif() - -# XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF -set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) - -# XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON -# -# address sanitizer build complains about _FORTIFY_SOURCE redefines -# In file included from :460: -# :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] -# #define _FORTIFY_SOURCE 2 -# -set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) - -# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro -if(XO_ADDRESS_SANITIZE) - set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) -else() - set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) -endif() - -# ---------------------------------------------------------------- -# generally want all the errors+warnings! -# however: address sanitizer generates error on _FORTIFY_SOURCE -# -macro(xo_compile_options target) - target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) -endmacro() - -# ---------------------------------------------------------------- -# use this for a subdir that builds a library -# and supports 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 CONFIG REQUIRED) - #add_dependencies(${target} indentlog) - target_link_libraries(${target} PUBLIC indentlog) - #target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include) -endmacro() From f4442c07a7b3ce23e1e883ddde011be8e28b5cac Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:54:02 -0400 Subject: [PATCH 0095/2524] xo-cmake: xo_toplevel_compile_options ++ set CMAKE_INSTALL_RPATH --- cmake/xo_cxx.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index e435de55..2307b06f 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -26,6 +26,11 @@ macro(xo_toplevel_compile_options) else() set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) endif() + + if(NOT CMAKE_INSTALL_RPATH) + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING + "runpath in installed libraries/executables") + endif() endif() # ---------------------------------------------------------------- From fb5bb1b03a1f2a6b9250dab078f0810c019ba527 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:57:54 -0400 Subject: [PATCH 0096/2524] randomgen: build streamlining using xo-cmake macros --- CMakeLists.txt | 15 ++++++--------- example/ex1/CMakeLists.txt | 2 +- example/ex2/CMakeLists.txt | 4 ++-- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 16cb2202..e27486bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,15 +6,13 @@ project(randomgen VERSION 0.1) enable_language(CXX) include(xo_macros/xo_cxx) -include(cmake/cxx.cmake) -include(cmake/code-coverage.cmake) +include(xo_macros/code-coverage) +#include(cmake/cxx.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. @@ -50,9 +48,8 @@ endif() if(NOT CMAKE_INSTALL_PREFIX) set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") endif() -if(NOT CMAKE_INSTALL_RPATH) - set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath in installed libraries/executables") -endif() + +xo_toplevel_compile_options() # ---------------------------------------------------------------- # external dependencies @@ -60,7 +57,7 @@ endif() # set CMAKE_INSTALL_PREFIX to analog of /usr # to use .cmake assistants from /usr/lib/cmake/indentlog # -find_package(indentlog REQUIRED) +#find_package(indentlog REQUIRED) # ---------------------------------------------------------------- @@ -69,6 +66,6 @@ add_subdirectory(example) # ---------------------------------------------------------------- -install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) +xo_install_include_tree() install(TARGETS ex1 DESTINATION bin/randomgen/example) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt index 6af29d79..a9d1b27d 100644 --- a/example/ex1/CMakeLists.txt +++ b/example/ex1/CMakeLists.txt @@ -1,2 +1,2 @@ add_executable(ex1 ex1.cpp) -xo_include_options(ex1) +xo_include_options2(ex1) diff --git a/example/ex2/CMakeLists.txt b/example/ex2/CMakeLists.txt index bab03164..ee6687f2 100644 --- a/example/ex2/CMakeLists.txt +++ b/example/ex2/CMakeLists.txt @@ -1,3 +1,3 @@ add_executable(ex2 ex2.cpp) -xo_include_options(ex2) -xo_indentlog_dependency(ex2) +xo_include_options2(ex2) +xo_internal_dependency(ex2 indentlog) From 29d5d68319a2563ed35e6fdc673d0f1fa30c5502 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 17:59:47 -0400 Subject: [PATCH 0097/2524] refcnt: build: xo-macros defaults CMAKE_MODULE_PATH --- CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bc8e2ef1..1b0b2a73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,10 +41,6 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") xo_toplevel_compile_options() -if(NOT CMAKE_INSTALL_RPATH) - set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") -endif() - # ---------------------------------------------------------------- # sources From ee054b6f37c8f042c2e108ad8ca75c14b733517f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:00:16 -0400 Subject: [PATCH 0098/2524] reflect: build: xo-macros defaults CMAKE_MODULE_PATH --- CMakeLists.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ec69e9c5..85a6b475 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,14 +46,6 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") xo_toplevel_compile_options() -if(NOT CMAKE_INSTALL_RPATH) - set(CMAKE_INSTALL_RPATH $(CMAKE_INSTALL_PREFIX)/lib CACHE STRING "runpath in installed libraries/executables") -endif() - -# early find_package() experiment -#find_package(indentlog CONFIG REQUIRED) -#find_package(refcnt CONFIG REQUIRED) - # ---------------------------------------------------------------- # sources From 4012d7387f2e371e95e92546806d1b6be6630008 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:00:48 -0400 Subject: [PATCH 0099/2524] subsys: build: xo-macros default CMAKE_MODULE_PATH --- CMakeLists.txt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 126d305b..d308603a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,8 +28,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # always write compile_commands.json set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") -xo_toplevel_compile_options() - # ---------------------------------------------------------------- # - author's convenience: default install prefix to /home/$USER/local # - otherwise use -DCMAKE_INSTALL_PREFIX=/path/to/somewhere @@ -44,9 +42,8 @@ endif() if(NOT CMAKE_INSTALL_PREFIX) set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") endif() -if(NOT CMAKE_INSTALL_RPATH) - set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath in installed libraries/executables") -endif() + +xo_toplevel_compile_options() #add_subdirectory(example) #add_subdirectory(utest) From 3d9d49f617de3aea350e7383ad1075a4cab14870 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:10:31 -0400 Subject: [PATCH 0100/2524] xo-cmake: consolidate CMAKE_CXX_STANDARD setting --- cmake/xo_cxx.cmake | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index c7f163d2..b02ad99a 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -1,5 +1,12 @@ macro(xo_toplevel_compile_options) + if(NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) + endif() + if(NOT DEFINED CMAKE_CXX_STANDARD_REQUIRED) + set(CMAKE_CXX_STANDARD_REQUIRED True) + endif() + # ---------------------------------------------------------------- # variable # XO_ADDRESS_SANITIZE From 3ec624bd75d0801ffcb28fdf57f74a205cede9aa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:11:37 -0400 Subject: [PATCH 0101/2524] subsys: consolidate CMAKE_CXX_STANDARD setting --- CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d308603a..24b9e54a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,12 +19,6 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) set(XO_PROJECT_NAME subsys) -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 "") From 3ab6046e2870edf5129417fe890950440ca7ca1c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:12:15 -0400 Subject: [PATCH 0102/2524] reflect: consolidate CMAKE_CXX_STANDARD setting --- CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 85a6b475..d338456c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,12 +35,6 @@ set(PROJECT_CXX_FLAGS "") 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 "") From c361c5d4ca988c8d67120a633c8ba4ddd29e0dab Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:13:15 -0400 Subject: [PATCH 0103/2524] refcnt: consolidate CMAKE_CXX_STANDARD setting --- CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b0b2a73..733ff9f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,12 +30,6 @@ 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 "") From cf4a928783ed0595d131ea721da435eab1263686 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:14:37 -0400 Subject: [PATCH 0104/2524] randomgen: consolidate CMAKE_CXX_STANDARD setting --- CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e27486bb..83076439 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,12 +29,6 @@ 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 "") From 401fc257e88ff2178b9f129f476e50836095ea98 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:21:27 -0400 Subject: [PATCH 0105/2524] xo-cmake: consolidate CMAKE_EXPORT_COMPILE_COMMANDS --- cmake/xo_cxx.cmake | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index b02ad99a..53cccb58 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -34,6 +34,13 @@ macro(xo_toplevel_compile_options) set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) endif() + # writes ${PROJECT_BINARY_DIR}/compile_commands.json; + # (symlink from toplevel git dir to tell LSP how to build) + # + if(NOT DEFINED CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") + endif() + if(NOT CMAKE_INSTALL_RPATH) set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath in installed libraries/executables") From 11092bc4fd0ce795e62f058a632ab0fe4d947c8a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:22:08 -0400 Subject: [PATCH 0106/2524] randomgen: consolidate CMAKE_EXPORT_COMPILE_COMMANDS + tidy --- CMakeLists.txt | 11 - cmake/code-coverage.cmake | 678 -------------------------------------- cmake/cxx.cmake | 40 --- 3 files changed, 729 deletions(-) delete mode 100644 cmake/code-coverage.cmake delete mode 100644 cmake/cxx.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 83076439..5653ec33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,20 +29,9 @@ set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -# always write compile_commands.json -set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") - # ---------------------------------------------------------------- # default install -if(NOT USER) - set(USER $ENV{USER}) -endif() - -if(NOT CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") -endif() - xo_toplevel_compile_options() # ---------------------------------------------------------------- diff --git a/cmake/code-coverage.cmake b/cmake/code-coverage.cmake deleted file mode 100644 index b6b36064..00000000 --- a/cmake/code-coverage.cmake +++ /dev/null @@ -1,678 +0,0 @@ -# -# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy of -# the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations under -# the License. - -# USAGE: To enable any code coverage instrumentation/targets, the single CMake -# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or -# on the command line. -# -# From this point, there are two primary methods for adding instrumentation to -# targets: -# -# 1 - A blanket instrumentation by calling `add_code_coverage()`, where -# all targets in that directory and all subdirectories are automatically -# instrumented. -# -# 2 - Per-target instrumentation by calling -# `target_code_coverage()`, where the target is given and thus only -# that target is instrumented. This applies to both libraries and executables. -# -# To add coverage targets, such as calling `make ccov` to generate the actual -# coverage information for perusal or consumption, call -# `target_code_coverage()` on an *executable* target. -# -# Example 1: All targets instrumented -# -# In this case, the coverage information reported will will be that of the -# `theLib` library target and `theExe` executable. -# -# 1a: Via global command -# -# ~~~ -# add_code_coverage() # Adds instrumentation to all targets -# -# add_library(theLib lib.cpp) -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target -# # (instrumentation already added via global anyways) -# # for generating code coverage reports. -# ~~~ -# -# 1b: Via target commands -# -# ~~~ -# add_library(theLib lib.cpp) -# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. -# -# add_executable(theExe main.cpp) -# target_link_libraries(theExe PRIVATE theLib) -# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. -# ~~~ -# -# Example 2: Target instrumented, but with regex pattern of files to be excluded -# from report -# -# ~~~ -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ -# -# Example 3: Target added to the 'ccov' and 'ccov-all' targets -# -# ~~~ -# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. -# -# add_executable(theExe main.cpp non_covered.cpp) -# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. -# ~~~ - -# Options -option( - CODE_COVERAGE - "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" - OFF) - -# Programs -find_program(LLVM_COV_PATH llvm-cov) -find_program(LLVM_PROFDATA_PATH llvm-profdata) -find_program(LCOV_PATH lcov) -find_program(GENHTML_PATH genhtml) -# Hide behind the 'advanced' mode flag for GUI/ccmake -mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) - -# Variables -set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) -set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) - -# Common initialization/checks -if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) - set(CODE_COVERAGE_ADDED ON) - - # Common Targets - add_custom_target( - ccov-preprocessing - COMMAND ${CMAKE_COMMAND} -E make_directory - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} - DEPENDS ccov-clean) - - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - # Messages - message(STATUS "Building with llvm Code Coverage Tools") - - if(NOT LLVM_COV_PATH) - message(FATAL_ERROR "llvm-cov not found! Aborting.") - else() - # Version number checking for 'EXCLUDE' compatibility - execute_process(COMMAND ${LLVM_COV_PATH} --version - OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) - string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION - ${LLVM_COV_VERSION_CALL_OUTPUT}) - - if(LLVM_COV_VERSION VERSION_LESS "7.0.0") - message( - WARNING - "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" - ) - endif() - endif() - - # Targets - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E remove -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - else() - add_custom_target( - ccov-clean - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND ${CMAKE_COMMAND} -E rm -f - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) - endif() - - # Used to get the shared object file list before doing the main all- - # processing - add_custom_target( - ccov-libs - COMMAND ; - COMMENT "libs ready for coverage report.") - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - # Messages - message(STATUS "Building with lcov Code Coverage Tools") - - if(CMAKE_BUILD_TYPE) - string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) - if(NOT ${upper_build_type} STREQUAL "DEBUG") - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - else() - message( - WARNING - "Code coverage results with an optimized (non-Debug) build may be misleading" - ) - endif() - if(NOT LCOV_PATH) - message(FATAL_ERROR "lcov not found! Aborting...") - endif() - if(NOT GENHTML_PATH) - message(FATAL_ERROR "genhtml not found! Aborting...") - endif() - - # Targets - add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory - ${CMAKE_BINARY_DIR} --zerocounters) - - else() - message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") - endif() -endif() - -# Adds code coverage instrumentation to a library, or instrumentation/targets -# for an executable target. -# ~~~ -# EXECUTABLE ADDED TARGETS: -# GCOV/LCOV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# -# LLVM-COV: -# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. -# ccov-${TARGET_NAME} : Generates HTML code coverage report. -# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. -# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. -# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. -# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. -# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. -# ccov-all-export : Exports the coverage report to a JSON file. -# -# Required: -# TARGET_NAME - Name of the target to generate code coverage for. -# Optional: -# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. -# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. -# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) -# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. -# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. -# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory -# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** -# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output -# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call -# ~~~ -function(target_code_coverage TARGET_NAME) - # Argument parsing - set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) - set(single_value_keywords COVERAGE_TARGET_NAME) - set(multi_value_keywords EXCLUDE OBJECTS ARGS) - cmake_parse_arguments( - target_code_coverage "${options}" "${single_value_keywords}" - "${multi_value_keywords}" ${ARGN}) - - # Set the visibility of target functions to PUBLIC, INTERFACE or default to - # PRIVATE. - if(target_code_coverage_PUBLIC) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY PUBLIC) - elseif(target_code_coverage_INTERFACE) - set(TARGET_VISIBILITY INTERFACE) - set(TARGET_LINK_VISIBILITY INTERFACE) - elseif(target_code_coverage_PLAIN) - set(TARGET_VISIBILITY PUBLIC) - set(TARGET_LINK_VISIBILITY) - else() - set(TARGET_VISIBILITY PRIVATE) - set(TARGET_LINK_VISIBILITY PRIVATE) - endif() - - if(NOT target_code_coverage_COVERAGE_TARGET_NAME) - # If a specific name was given, use that instead. - set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) - endif() - - if(CODE_COVERAGE) - - # Add code coverage instrumentation to the target's linker command - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} - -fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs - -ftest-coverage) - target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) - endif() - - # Targets - get_target_property(target_type ${TARGET_NAME} TYPE) - - # Add shared library to processing for 'all' targets - if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" >> - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - if(NOT TARGET ccov-libs) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-libs - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # For executables add targets to run and produce output - if(target_type STREQUAL "EXECUTABLE") - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # If there are shared objects to also work with, generate the string to - # add them here - foreach(SO_TARGET ${target_code_coverage_OBJECTS}) - # Check to see if the target is a shared object - if(TARGET ${SO_TARGET}) - get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) - if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") - set(SO_OBJECTS ${SO_OBJECTS} -object=$) - endif() - endif() - endforeach() - - # Run the executable, generating raw profile data Make the run data - # available for further processing. Separated to allow Windows to run - # this target serially. - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${CMAKE_COMMAND} -E env - LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw - $ ${target_code_coverage_ARGS} - COMMAND - ${CMAKE_COMMAND} -E echo "-object=$" - ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list - COMMAND - ${CMAKE_COMMAND} -E echo - "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" - >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list - JOB_POOL ccov_serial_pool - DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) - - # Merge the generated profile data so llvm-cov can process it - add_custom_target( - ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_PROFDATA_PATH} merge -sparse - ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o - ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Ignore regex only works on LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print out details of the coverage information to the command line - add_custom_target( - ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Print out a summary of the coverage information to the command line - add_custom_target( - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} report $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} export $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${LLVM_COV_PATH} show $ ${SO_OBJECTS} - -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO - "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" - ) - - # Run the executable, generating coverage information - add_custom_target( - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND $ ${target_code_coverage_ARGS} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - - # Generate exclusion string for use - foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - if(NOT ${target_code_coverage_EXTERNAL}) - set(EXTERNAL_OPTION --no-external) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - else() - add_custom_target( - ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters - COMMAND $ ${target_code_coverage_ARGS} - COMMAND - ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory - ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file - ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ${TARGET_NAME}) - endif() - - # Generates HTML output of the coverage information for perusal - add_custom_target( - ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - COMMAND - ${GENHTML_PATH} -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} - ${COVERAGE_INFO} - DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - - add_custom_command( - TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." - ) - - # AUTO - if(target_code_coverage_AUTO) - if(NOT TARGET ccov) - add_custom_target(ccov) - endif() - add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) - - if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID - MATCHES "GNU") - if(NOT TARGET ccov-report) - add_custom_target(ccov-report) - endif() - add_dependencies( - ccov-report - ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - - # ALL - if(target_code_coverage_ALL) - if(NOT TARGET ccov-all-processing) - message( - FATAL_ERROR - "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." - ) - endif() - - add_dependencies(ccov-all-processing - ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) - endif() - endif() - endif() -endfunction() - -# Adds code coverage instrumentation to all targets in the current directory and -# any subdirectories. To add coverage instrumentation to only specific targets, -# use `target_code_coverage`. -function(add_code_coverage) - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - add_compile_options(-fprofile-instr-generate -fcoverage-mapping) - add_link_options(-fprofile-instr-generate -fcoverage-mapping) - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - add_compile_options(-fprofile-arcs -ftest-coverage) - link_libraries(gcov) - endif() - endif() -endfunction() - -# Adds the 'ccov-all' type targets that calls all targets added via -# `target_code_coverage` with the `ALL` parameter, but merges all the coverage -# data from them into a single large report instead of the numerous smaller -# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for -# use with coverage dashboards (e.g. codecov.io, coveralls). -# ~~~ -# Optional: -# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! -# ~~~ -function(add_code_coverage_all_targets) - # Argument parsing - set(multi_value_keywords EXCLUDE) - cmake_parse_arguments(add_code_coverage_all_targets "" "" - "${multi_value_keywords}" ${ARGN}) - - if(CODE_COVERAGE) - if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" - OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") - - # Merge the profile data for all of the run executables - if(WIN32) - add_custom_target( - ccov-all-processing - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe - merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -sparse $$FILELIST) - else() - add_custom_target( - ccov-all-processing - COMMAND - ${LLVM_PROFDATA_PATH} merge -o - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) - endif() - - # Regex exclude only available for LLVM >= 7 - if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} - -ignore-filename-regex='${EXCLUDE_ITEM}') - endforeach() - endif() - - # Print summary of the code coverage information to the command line - if(WIN32) - add_custom_target( - ccov-all-report - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe - report $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all-report - COMMAND - ${LLVM_COV_PATH} report `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - # Export coverage information so continuous integration tools (e.g. - # Jenkins) can consume it - add_custom_target( - ccov-all-export - COMMAND - ${LLVM_COV_PATH} export `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -format="text" ${EXCLUDE_REGEX} > - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json - DEPENDS ccov-all-processing) - - # Generate HTML output of all added targets for perusal - if(WIN32) - add_custom_target( - ccov-all - COMMAND - powershell -Command $$FILELIST = Get-Content - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show - $$FILELIST - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - else() - add_custom_target( - ccov-all - COMMAND - ${LLVM_COV_PATH} show `cat - ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` - -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata - -show-line-counts-or-regions - -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - -format="html" ${EXCLUDE_REGEX} - DEPENDS ccov-all-processing) - endif() - - elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES - "GNU") - set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") - - # Nothing required for gcov - add_custom_target(ccov-all-processing COMMAND ;) - - # Exclusion regex string creation - set(EXCLUDE_REGEX) - foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) - set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} - '${EXCLUDE_ITEM}') - endforeach() - - if(EXCLUDE_REGEX) - set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file - ${COVERAGE_INFO}) - else() - set(EXCLUDE_COMMAND ;) - endif() - - # Capture coverage data - if(${CMAKE_VERSION} VERSION_LESS "3.17.0") - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - else() - add_custom_target( - ccov-all-capture - COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} - COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture - --output-file ${COVERAGE_INFO} - COMMAND ${EXCLUDE_COMMAND} - DEPENDS ccov-preprocessing ccov-all-processing) - endif() - - # Generates HTML output of all targets for perusal - add_custom_target( - ccov-all - COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged - ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} - DEPENDS ccov-all-capture) - - endif() - - add_custom_command( - TARGET ccov-all - POST_BUILD - COMMAND ; - COMMENT - "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." - ) - endif() -endfunction() diff --git a/cmake/cxx.cmake b/cmake/cxx.cmake deleted file mode 100644 index e01e6801..00000000 --- a/cmake/cxx.cmake +++ /dev/null @@ -1,40 +0,0 @@ -# ---------------------------------------------------------------- -# 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 - ${PROJECT_SOURCE_DIR}/include - ${PROJECT_BINARY_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() -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() From 4600ebcb213914af4637b20eaad5cae5bd667dff Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:22:54 -0400 Subject: [PATCH 0107/2524] subsys: consolidate CMAKE_EXPORT_COMPILE_COMMANDS + tidy --- CMakeLists.txt | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24b9e54a..189db40a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,38 +19,13 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) set(XO_PROJECT_NAME subsys) -# always write compile_commands.json -set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") - -# ---------------------------------------------------------------- -# - author's convenience: default install prefix to /home/$USER/local -# - otherwise use -DCMAKE_INSTALL_PREFIX=/path/to/somewhere - -if(NOT USER) - set(USER $ENV{USER}) -endif() - -# hmm. this works if explicitly given with cmake: -# cmake -DCMAKE_INSTALL_PREFIX=/home/roland/local path/to/source -# but not as default -if(NOT CMAKE_INSTALL_PREFIX) - set(CMAKE_INSTALL_PREFIX /home/${USER}/local CACHE STRING "install directory") -endif() - xo_toplevel_compile_options() -#add_subdirectory(example) -#add_subdirectory(utest) - # ---------------------------------------------------------------- # installing header-only library add_library(subsys INTERFACE) xo_include_headeronly_options2(subsys) -#target_include_directories(subsys INTERFACE -# $ -# $ -#) xo_install_library2(subsys) # ---------------------------------------------------------------- From 277935c25e202124a4973476dac62c33e3f1ff5c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:23:40 -0400 Subject: [PATCH 0108/2524] randomgen: consolidate CMAKE_EXPORT_COMPILE_COMMANDS --- CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 733ff9f9..aef9d414 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,9 +30,6 @@ set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -# always write compile_commands.json -set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") - xo_toplevel_compile_options() # ---------------------------------------------------------------- From 5972be91da909a87ee856d1e940605f9f672f21a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 18:23:57 -0400 Subject: [PATCH 0109/2524] randomgen: consolidate CMAKE_EXPORT_COMPILE_COMMANDS --- CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d338456c..30a22fb6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,9 +35,6 @@ set(PROJECT_CXX_FLAGS "") add_definitions(${PROJECT_CXX_FLAGS}) -# always write compile_commands.json -set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") - xo_toplevel_compile_options() # ---------------------------------------------------------------- From f38f48943762a2bc72efd27ef1ee7dbab7ce6ee2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 19:40:39 -0400 Subject: [PATCH 0110/2524] xo-cmake: + xo_add_shared_library() + misc --- cmake/xo_cxx.cmake | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 53cccb58..7fe1c65d 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -47,11 +47,7 @@ macro(xo_toplevel_compile_options) endif() endmacro() -# ---------------------------------------------------------------- -# use this in subdirs that compile c++ code. -# do not use for header-only subsystems; see xo_include_headeronly_options2() -# -macro(xo_include_options2 target) +macro(xo_include_headeronly_options2 target) # ---------------------------------------------------------------- # PROJECT_SOURCE_DIR: # so we can for example write @@ -63,7 +59,7 @@ macro(xo_include_options2 target) # compiler's include path # target_include_directories( - ${target} PUBLIC + ${target} INTERFACE $ # e.g. for #include "indentlog/scope.hpp" $ $ # e.g. for #include "Refcounted.hpp" in refcnt/src @@ -82,7 +78,37 @@ macro(xo_include_options2 target) endif() endmacro() -macro(xo_include_headeronly_options2 target) +# ---------------------------------------------------------------- +# use this for a shared library. +# +macro(xo_add_shared_library target targetversion soversion sources) + add_library(${target} SHARED ${sources}) + foreach(arg IN ITEMS ${ARGN}) + #message("target=${target}; arg=${arg}") + + # to use PUBLIC here would need to split: + # $ + # $ + # but shouldn't need that, since we arrange to install includes via + # xo_include_options2() below + # + target_sources(${target} PRIVATE ${arg}) + endforeach() + set_target_properties( + ${target} + PROPERTIES + VERSION ${targetversion} + SOVERSION ${soversion}) + xo_compile_options(${target}) + xo_include_options2(${target}) + xo_install_library2(${target}) +endmacro() + +# ---------------------------------------------------------------- +# use this in subdirs that compile c++ code. +# do not use for header-only subsystems; see xo_include_headeronly_options2() +# +macro(xo_include_options2 target) # ---------------------------------------------------------------- # PROJECT_SOURCE_DIR: # so we can for example write @@ -94,7 +120,7 @@ macro(xo_include_headeronly_options2 target) # compiler's include path # target_include_directories( - ${target} INTERFACE + ${target} PUBLIC $ # e.g. for #include "indentlog/scope.hpp" $ $ # e.g. for #include "Refcounted.hpp" in refcnt/src From 4e0c8e92f236ebfce2b345b686f8b45fc26ba8b7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 19:41:26 -0400 Subject: [PATCH 0111/2524] refcnt: use xo_add_shared_library() from xo-cmake --- src/CMakeLists.txt | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0a60fbea..9d006fd9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,14 +1,5 @@ set(SELF_LIBRARY_NAME refcnt) set(SELF_SOURCE_FILES Refcounted.cpp Displayable.cpp) -add_library(${SELF_LIBRARY_NAME} SHARED ${SELF_SOURCE_FILES}) - -set_target_properties(${SELF_LIBRARY_NAME} - PROPERTIES - VERSION ${PROJECT_VERSION} - SOVERSION 1) +xo_add_shared_library(${SELF_LIBRARY_NAME} ${PROJECT_VERSION} 1 ${SELF_SOURCE_FILES}) xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) - -xo_include_options2(${SELF_LIBRARY_NAME}) -xo_compile_options(${SELF_LIBRARY_NAME}) -xo_install_library2(${SELF_LIBRARY_NAME}) From 94d77bf809900b2458f87dfe43d8698b1910230c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 27 Sep 2023 19:42:52 -0400 Subject: [PATCH 0112/2524] reflect: use xo_add_shared_library() + misc. --- CMakeLists.txt | 2 -- src/reflect/CMakeLists.txt | 16 +--------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 30a22fb6..0402062d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,10 +29,8 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* # ---------------------------------------------------------------- # c++ settings -set(XO_PROJECT_NAME reflect) set(PROJECT_CXX_FLAGS "") #set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") - add_definitions(${PROJECT_CXX_FLAGS}) xo_toplevel_compile_options() diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index 0b5c710e..6056ebca 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -3,21 +3,7 @@ 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_options2(${SELF_LIBRARY_NAME}) -xo_install_library2(${SELF_LIBRARY_NAME}) +xo_add_shared_library(${SELF_LIBRARY_NAME} ${PROJECT_VERSION} 1 ${SELF_SOURCE_FILES}) # ---------------------------------------------------------------- # dependencies: logutil, ... From 2b10fec175580c033a60efb042fb9a8903843ba9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 17:22:42 -0400 Subject: [PATCH 0113/2524] + xo_external_target_dependency --- cmake/xo_cxx.cmake | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 7fe1c65d..66ac7962 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -224,6 +224,20 @@ macro(xo_internal_dependency target dep) target_link_libraries(${target} PUBLIC ${dep}) endmacro() +# dependency on namespaced target +# e.g. +# add_library(foo ..) or add_executable(foo ...) +# then +# xo_external_namespaced_dependency(foo Catch2 Catch2::Catch2) +# equivalent to +# find_package(Catch2 CONFIG REQUIRED) +# target_link_libraries(foo PUBLIC Catch2::Catch2) +# +macro(xo_external_target_dependency target pkg pkgtarget) + find_package(${pkg} CONFIG REQUIRED) + target_link_libraries(${target} PUBLIC ${pkgtarget}) +endmacro() + # dependency on target provided from this codebase. # # 1. don't need find_package() in this case, since details of dep targets @@ -234,3 +248,6 @@ endmacro() macro(xo_self_dependency target dep) target_link_libraries(${target} PUBLIC ${dep}) endmacro() + +# ---------------------------------------------------------------- +# From ac265dc2b709eb97c22a5e8d004369e6928671af Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 17:36:50 -0400 Subject: [PATCH 0114/2524] build: clenaup dependencies --- src/CMakeLists.txt | 3 ++- utest/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8083cee0..43fe7a82 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,7 +7,8 @@ set_target_properties(${SELF_LIBRARY_NAME} VERSION ${PROJECT_VERSION} SOVERSION 1) -xo_indentlog_dependency(${SELF_LIBRARY_NAME}) +xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) +#xo_indentlog_dependency(${SELF_LIBRARY_NAME}) xo_include_options(${SELF_LIBRARY_NAME}) xo_compile_options(${SELF_LIBRARY_NAME}) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 74706592..cfdf594a 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -35,7 +35,7 @@ target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC refcnt) # ---------------------------------------------------------------- # 3rd part dependency: catch2: -find_package(Catch2 2 REQUIRED) +xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) # need this so that catch2/include appears in compile_commands.json, # on which lsp integration relies. From 592447fa153ea2ccfc3bb65bb0ed9f6ba6db0789 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 17:37:50 -0400 Subject: [PATCH 0115/2524] + .gitignore --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..86d062ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# symlink to ${mybuilddirectory}/compile_commands.json for LSP +compile_commands.json +# LSP keeps state here +.cache +# typical build directories +build +ccov From 4e88ca9820e2dc1c0bd610816af33fb5cd6bc7be Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 21:55:52 -0400 Subject: [PATCH 0116/2524] build: refcnt: streamline utest build --- utest/CMakeLists.txt | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index cfdf594a..ef9a4006 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -1,11 +1,11 @@ # build unittest 'refcnt/utest/utest.refcnt set(SELF_EXECUTABLE_NAME utest.refcnt) - # These tests can use the Catch2-provided main set(SELF_SOURCE_FILES intrusive_ptr.test.cpp refcnt_utest_main.cpp) + add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) -xo_include_options(${SELF_EXECUTABLE_NAME}) +xo_include_options2(${SELF_EXECUTABLE_NAME}) add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) @@ -15,17 +15,17 @@ 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}) +## 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: refcnt, ... @@ -33,7 +33,7 @@ target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC refcnt) # ---------------------------------------------------------------- -# 3rd part dependency: catch2: +# 3rd party dependency: catch2: xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) From 415eeaaca1476754e28f538e0a998515342bb587 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 22:18:16 -0400 Subject: [PATCH 0117/2524] build tweaks --- .github/workflows/cmake-single-platform.yml | 3 ++- utest/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index a8ce3a72..77554e15 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -20,7 +20,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: checkout source + uses: actions/checkout@v3 - name: Install catch2 # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 7eae2825..990802e3 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -27,7 +27,7 @@ target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC # ---------------------------------------------------------------- # internal dependencies: logutil, ... -target_link_libraries(${SELF_EXECUTABLE_NAME} PUBLIC reflect) +xo_self_dependency(${SELF_EXECUTABLE_NAME} reflect) # ---------------------------------------------------------------- # 3rd part dependency: catch2: From ee436ca9e96ec2c0f96edbb73e79debf11979867 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 3 Oct 2023 22:22:10 -0400 Subject: [PATCH 0118/2524] build: technical fix to catch2 dep --- utest/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 09444df5..291f6d16 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -33,7 +33,7 @@ xo_self_dependency(${SELF_EXECUTABLE_NAME} reflect) # ---------------------------------------------------------------- # 3rd party dependency: catch2: -find_package(Catch2 2 REQUIRED) +xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) # need this so that catch2/include appears in compile_commands.json, # on which lsp integration relies. From 90dcd600c07068fc4162e1068941fae983f38d87 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 00:02:38 -0400 Subject: [PATCH 0119/2524] build: + cmake find_package() support --- CMakeLists.txt | 25 +++++++++++++++++++------ README.md | 3 ++- cmake/randomgenConfig.cmake.in | 4 ++++ 3 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 cmake/randomgenConfig.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 5653ec33..87383904 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -# using indentlog/CMakeLists.txt as model +# randomgen/CMakeLists.txt cmake_minimum_required(VERSION 3.10) @@ -23,14 +23,14 @@ add_code_coverage() add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) # ---------------------------------------------------------------- -# c++ settings - -set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +# bespoke (usually temporary) c++ settings +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) # ---------------------------------------------------------------- -# default install +# common include paths etc. xo_toplevel_compile_options() @@ -40,7 +40,7 @@ xo_toplevel_compile_options() # set CMAKE_INSTALL_PREFIX to analog of /usr # to use .cmake assistants from /usr/lib/cmake/indentlog # -#find_package(indentlog REQUIRED) +# xo_internal_dependency(..) # ---------------------------------------------------------------- @@ -48,7 +48,20 @@ add_subdirectory(example) #add_subdirectory(utest) # ---------------------------------------------------------------- +# output targets +set(SELF_LIB randomgen) +add_library(${SELF_LIB} INTERFACE) +xo_include_headeronly_options2(${SELF_LIB}) + +# ---------------------------------------------------------------- +# standard install + provide find_package() support + +xo_install_library2(${SELF_LIB}) xo_install_include_tree() +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install additional components install(TARGETS ex1 DESTINATION bin/randomgen/example) diff --git a/README.md b/README.md index 905f267e..73f13f1b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ $ cd randomgen $ mkdir build $ cd build -$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=$(INSTALL_PREFIX) .. +$ PREFIX=/usr/local # for example +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=$(PREFIX) -DCMAKE_INSTALL_PREFIX=${PREFIX} .. $ make $ make install ``` diff --git a/cmake/randomgenConfig.cmake.in b/cmake/randomgenConfig.cmake.in new file mode 100644 index 00000000..e66430b0 --- /dev/null +++ b/cmake/randomgenConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/randomgenTargets.cmake") +check_required_components("@PROJECT_NAME@") From f6e0dc2182080f5a528127490105596357711d41 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:23:45 -0400 Subject: [PATCH 0120/2524] initial impl --- CMakeLists.txt | 52 + cmake/xo_treeConfig.cmake.in | 5 + include/xo/tree/BplusTree.hpp | 1799 ++++++++++ include/xo/tree/RedBlackTree.hpp | 3158 ++++++++++++++++++ include/xo/tree/bplustree/BplusTreeUtil.hpp | 280 ++ include/xo/tree/bplustree/GenericNode.hpp | 123 + include/xo/tree/bplustree/InternalNode.hpp | 768 +++++ include/xo/tree/bplustree/Iterator.hpp | 355 ++ include/xo/tree/bplustree/IteratorUtil.hpp | 56 + include/xo/tree/bplustree/LeafNode.hpp | 684 ++++ include/xo/tree/bplustree/Lhs.hpp | 68 + include/xo/tree/bplustree/bplustree_tags.hpp | 16 + utest/CMakeLists.txt | 47 + utest/bplustree.cpp | 813 +++++ utest/random_tree_ops.hpp | 450 +++ utest/redblacktree.cpp | 248 ++ utest/tree_utest_main.cpp | 7 + 17 files changed, 8929 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/xo_treeConfig.cmake.in create mode 100644 include/xo/tree/BplusTree.hpp create mode 100644 include/xo/tree/RedBlackTree.hpp create mode 100644 include/xo/tree/bplustree/BplusTreeUtil.hpp create mode 100644 include/xo/tree/bplustree/GenericNode.hpp create mode 100644 include/xo/tree/bplustree/InternalNode.hpp create mode 100644 include/xo/tree/bplustree/Iterator.hpp create mode 100644 include/xo/tree/bplustree/IteratorUtil.hpp create mode 100644 include/xo/tree/bplustree/LeafNode.hpp create mode 100644 include/xo/tree/bplustree/Lhs.hpp create mode 100644 include/xo/tree/bplustree/bplustree_tags.hpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/bplustree.cpp create mode 100644 utest/random_tree_ops.hpp create mode 100644 utest/redblacktree.cpp create mode 100644 utest/tree_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..2fc26356 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +# xo-tree/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_tree VERSION 0.1) +enable_language(CXX) + +# common XO macros (see github:Rconybea/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() +# enable code coverage for all executables+libraries +# (when configured with -DCODE_COVERAGE=ON) +# +add_code_coverage() +add_code_coverage_all_targets( + EXCLUDE + /nix/store/* + ${PROJECT_SOURCE_DIR}/utest/*) + +# ---------------------------------------------------------------- +# c++ settings + +# sets XO_COMPILE_OPTIONS +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# output targets + +add_subdirectory(utest) + +set(SELF_LIB xo_tree) + +add_library(${SELF_LIB} INTERFACE) +xo_include_headeronly_options2(${SELF_LIB}) + +# ---------------------------------------------------------------- +# +xo_install_library2(${PROJECT_NAME}) +xo_install_include_tree() +# (note: ..Targets from xo_install_library2()) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# input dependencies + +xo_dependency_headeronly(${SELF_LIB} randomgen) + diff --git a/cmake/xo_treeConfig.cmake.in b/cmake/xo_treeConfig.cmake.in new file mode 100644 index 00000000..8fbf8cb5 --- /dev/null +++ b/cmake/xo_treeConfig.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") + diff --git a/include/xo/tree/BplusTree.hpp b/include/xo/tree/BplusTree.hpp new file mode 100644 index 00000000..f58b4317 --- /dev/null +++ b/include/xo/tree/BplusTree.hpp @@ -0,0 +1,1799 @@ +/* @file BplusTree.hpp */ + +/* provides B+ tree with order statistics */ + +/* NOTES: + * - expect optimimum node size to be OS page size. + * + */ + +#pragma once + +//#include "bplustree/BplusTreeNode.hpp" +#include "bplustree/LeafNode.hpp" +#include "bplustree/InternalNode.hpp" +#include "bplustree/Iterator.hpp" +#include "bplustree/Lhs.hpp" +#include "bplustree/bplustree_tags.hpp" +#include "indentlog/scope.hpp" +#include "indentlog/print/tag.hpp" +#include "indentlog/print/pad.hpp" +#include /* for std::unqiue_ptr */ +#include /* for std::max */ +#include /* for std::numeric_limits */ +#include +#include +#include +#if __APPLE__ && __MACH__ +# include +#endif + +namespace xo { + namespace tree { + /* + * +-------------+ + * | BplusTree | +--------------------+ + * | .properties +-----| BplusStdProperties | + * | .n_element | | .branching_factor | + * | .root | | .debug_flag | + * +------+------+ +--------------------+ + * | + * | .root + * | + * +-------------------+ +--------------+ +--------------+ + * | GenericNode | isa | LeafNode | .elt_v[i] | LeafNodeItem | + * | .node_type |<---+----| .elt_v[] +-------------| .kv_pair | + * | .parent | | | | | | + * | .n_elt | | +--------------+ +--------------+ + * | .branching_factor | | + * +-------------------+ | + * | + * | +--------------+ +------------------+ + * | | InternalNode | .elt_v[i] | InternalNodeItem | + * \----| .elt_v[] +-------------| .key | + * | | | .child | + * +--------------+ +------------------+ + * + * Invariants: + * - tree is always balanced -- every path from root to a LeafNode, visits the same number of InternalNodes. + * - all Nodes (both LeafNodes and InternalNodes) satisfy bf/2 <= .n_elt <= bf (where bf = BplusTree.properties.branching_factor) + * Details + * - if InternalNode p has p.elt_v[i].child = q, then q.parent = p + * - GenericNode.branching_factor = BplusTree.properties.branching_factor for all nodes in the same BplusTree + * + * Tree with 0 key/value pairs + * + * +--------------+ + * | BplusTree | + * | .root = null | + * +--------------+ + * + * Tree with [1 .. b] key/value pairs (with b = BplusTree.properties.branching_factor) + * + * +---------------+ + * | BplusTree | + * | .root = node1 | + * +--------+------+ + * | + * node1 | + * +----------------------------------------------+ + * | LeafNode | + * | .parent = null | + * | | + * | .elt_v[0] .elt_v[b-1] | b = BplusTree.properties.branching_factor - 1 + * | +----------------+- ... -+-----------------+ | .elt_v[i].kv_pair.first = i'th key + * | | k0 | v0 | | k(b-1) | v(b-1) | | .elt_v[i].kv_pair.second = i'th value + * | +----------------+- ... -+-----------------+ | + * +----------------------------------------------+ + * + * Tree with [b+1 ..] key/value pairs + * + * +---------------+ + * | BplusTree | + * | .root = node1 | + * +--------+------+ + * | + * node1 | + * +----------------------------------------------+ + * | InternalNode | + * | .parent = null | + * | | + * | .elt_v[0] .elt_v[b-1] | + * | +----------------+- ... -+-----------------+ | .elt_v[i].key = minimum key in subtree .elt_v[i].child + * | | k0 | node2 | | k(b-1) | node(b)| | + * | +----------------+- ... -+-----------------+ | + * | .key .child .key .child | + * +-------------------+-----+--------------------+ + * | | + * | | ... + * | | + * .elt_v[0].child | | .elt_v[1].child + * /--------------------/ \----------------------------\ + * | | + * node2 | node3 | + * +----------------------------------------------+ +-------------------------------------------------+ + * | LeafNode | | LeafNode | + * | .parent = node1 | | .parent = node1 | + * | | | | + * | .elt_v[0] .elt_v[b-1] | | .elt_v[0] .elt_v[b-1] | ..... + * | +----------------+- ... -+-----------------+ | | +--------+--------+- ... -+---------+---------+ | + * | | k0 | v0 | | k(b-1) | v(b-1) | | | | kb | vb | | k(2b-1) | v(2b-1) | | + * | +----------------+- ... -+-----------------+ | | +-----------------+- ... -+---------+---------+ | + * +----------------------------------------------+ +-------------------------------------------------+ + * + * + * Larger trees havedadditional levels comprising InternalNodes. + * + */ + + /* NullReduce: 0-size reduce function (disappears at compile time) */ + template + struct NullReduce; + + struct Machdep { + /* current page size (on a linux system). Probably 4K + * + * (a) need this to be at least large enough to + * hold 3 keys + * (b) note linux page size isn't fixed at compile time + */ + static inline std::size_t get_page_size() { + return ::sysconf(_SC_PAGESIZE); + } + + /* L1 cache line size (on a linux system). Probably 64 bytes */ + static inline std::size_t get_cache_line_size() { + // https://sourceforge.net/p/predef/wiki/OperatingSystems/ +# if __APPLE__ && __MACH__ + std::size_t line_size = 0; + std::size_t sizeof_line_size = sizeof(line_size); + ::sysctlbyname("hw.cachelinesize", + &line_size, &sizeof_line_size, 0, 0); + return line_size; +# else + return ::sysconf(_SC_LEVEL1_DCACHE_LINESIZE); +# endif + } + }; /*Machdep*/ + + /* B+ tree nodes come in several flavors: {root | internal | leaf}: + * + * - root. each B+ tree has exactly one node of this type, + * representing the root of the B+ tree. + * The root node is subject to fewer restrictions than other + * nodes in a B+ tree: + * - can have 2..b elements (where b is branching factor for this B+ tree). + * - can function as tree's one and only leaf node (if tree has <= b items). + * - can function as an internal node (if tree has > b items) + * + * - internal. an internal node has: + * - n child node pointers, subject to ceil(b/2) <= n <= b, + * where b is tree's branching factor + * - n keys. key[j] is the smallest key value in subtree j. + * see InternalNode + * + * - leaf. a leaf node has: + * - n keys, subject to ceil(b/2) <= n <= b, + * where b is tree's branching factor + * - n values. values are stored as pointers. + * - pointer to next leaf node, to streamline inorder traversal + */ + template + struct BplusStdProperties { + public: + using KeyType = Key; + using ValueType = Value; + + public: + BplusStdProperties() = default; + explicit BplusStdProperties(std::size_t bf, bool debug_flag) + : branching_factor_{bf}, debug_flag_{debug_flag} {} + + static constexpr tags::ordinal_tag ordinal_tag_value() { return OrdinalTag; } + static constexpr bool ordinal_enabled() { return OrdinalTag == tags::ordinal_enabled; } + + static constexpr std::size_t c_min_branching_factor = 3; + + /* compute branching factor for given (leaf) node size */ + static constexpr std::size_t branching_factor_for_size(std::size_t z) { + return std::max(c_min_branching_factor, + (z - sizeof(LeafNode)) + / (sizeof(LeafNodeItemPlaceholder))); + } /*branching_factor_for_size*/ + + /* default branching factor. + * attempt to optimize for cache efficiency of 'internal' nodes + * + * minimum branching factor always 3 + */ + static constexpr std::size_t default_branching_factor() { + return branching_factor_for_size(Machdep::get_page_size()); + } + + /* expect this will be min branching factor + * (i.e. smallest allowed LeafNode size likely won't fit in cache line): + * + * - cache line size = 64 bytes + * - leaf node overhead = 56 bytes + * - leaf node item size = 16 bytes + */ + static constexpr std::size_t default_cacheline_branching_factor() { + return branching_factor_for_size(Machdep::get_cache_line_size()); + } + + std::size_t branching_factor() const { return branching_factor_; } + bool debug_flag() const { return debug_flag_; } + + void set_debug_flag(bool x) { debug_flag_ = x; } + + private: + /* branching factor to use for both leaf an inteernal B+ tree nodes */ + std::size_t branching_factor_ = default_branching_factor(); + /* if true enable verbose logging during B+ tree operations */ + bool debug_flag_ = false; + }; /*BplusStdProperties*/ + + template + inline std::ostream & + operator<<(std::ostream & os, + BplusStdProperties const & p) + { + using xo::xtag; + + os << ""; + + return os; + } /*operator<<*/ + + /* B+ tree with order statistics + * + * require: + * - Key is equality comparable, and imposes total ordering on keys. + * - Key, Value, Reduce, Properties are copyable and null-constructible + * - Reduce.value_type = Accumulator + * - Reduce.operator() :: (Accumulator x Key) -> Accumulator + */ + template , + typename Properties = BplusStdProperties> + class BplusTree { + public: + using GenericNodeType = GenericNode; + using InternalNodeType = InternalNode; + using LeafNodeType = LeafNode; + using InternalNodeItemType = InternalNodeItem; + using LeafNodeItemType = LeafNodeItem; + using BpTreeConstLhs = detail::BplusTreeConstLhs>; + using BpTreeUtil = BplusTreeUtil; + + using key_type = Key; + using mapped_type = Value; + using value_type = std::pair; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + // key_compare + // allocator_type + using reference = value_type &; + using const_reference = value_type const &; + // pointer = std::allocator_traits::pointer; + // const_pointer = std::allocator_traits::const_pointer; + using const_iterator = detail::ConstIterator; + // reverse_iterator + // const_reverse_iterator + // value_compare (compares value_type objects by comparing their first elements) + + public: + BplusTree() = default; + explicit BplusTree(Properties const & properties) : properties_{properties} {} + + bool empty() const { return this->n_element_ == 0; } + size_type size() const { return this->n_element_; } + size_type max_size() const { return std::numeric_limits::max(); } + std::size_t branching_factor() const { return this->properties_.branching_factor(); } + + bool debug_flag() const { return this->properties_.debug_flag(); } + void set_debug_flag(bool x) { this->properties_.set_debug_flag(x); } + + /* verify b+ tree invariants. + * if invariants satisfied, return true. + * if not satisfied, + * - throw_flag=true -> throw execption + * - throw_flag=false -> return false; + */ + bool verify_ok(bool throw_flag = true) const { + using xo::scope; + using xo::xtag; + + //scope x("verify_ok"); + //x.log(xtag("n_element", this->n_element_)); + + std::size_t z = 0; + + try { + z = this->verify_helper(throw_flag); + } catch (...) { + if (throw_flag) + throw; + + return false; + } + + //x.log(xtag("z", z)); + + if (z != this->n_element_) { + if (throw_flag) { + std::string err = tostr("BplusTree::verify_ok" + ": bad key count", + xtag("expected", this->n_element_), + xtag("counted", z)); + + throw std::runtime_error(err); + } + + return false; + } + + return true; + } /*verify_ok*/ + + /* cxxx: const iterator + * rxxx: reverse iterator + * crxxx: const reverse iterator + */ + + const_iterator cprebegin() const { return const_iterator::prebegin_aux(this->leafnode_begin_); } + const_iterator cbegin() const { return const_iterator::begin_aux(this->leafnode_begin_); } + const_iterator cend() const { return const_iterator::end_aux(this->leafnode_end_); } + + const_iterator begin() const { return this->cbegin(); } + const_iterator end() const { return this->cend(); } + + const_iterator crprebegin() const { return const_iterator::rprebegin_aux(this->leafnode_end_); } + const_iterator crbegin() const { return const_iterator::rbegin_aux(this->leafnode_end_); } + const_iterator crend() const { return const_iterator::rend_aux(this->leafnode_begin_); } + + const_iterator rbegin() const { return this->crbegin(); } + const_iterator rend() const { return this->crend(); } + + /* find item with key equal to x in this tree. + * success -> return iterator ix with ix->first = x + * failure -> return iterator this->cend() + */ + const_iterator find(Key const & x) const { + FindNodeResult leaffindresult = this->find_leaf_node(x); + LeafNodeType const * leaf = leaffindresult.node(); + + std::pair lub_ix_recd = leaf->find_lub_ix(x); + + if (lub_ix_recd.first) { + return const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf, + lub_ix_recd.second - 1); + } else { + return this->cend(); + } + } /*find*/ + + /* find i'th key/value pair (in key order) in this tree. + * + * Require: + * - 0 <= i < .size + */ + const_iterator find_ith(std::size_t i_tree) const { + using xo::tostr; + using xo::xtag; + + if (i_tree >= this->size()) { + throw std::runtime_error(tostr("BplusTree::find_ith: expected index i in range [0..n)", + xtag("i", i_tree), + xtag("n", this->size()))); + } + + GenericNodeType * generic_node = this->root_.get(); + + return BplusTreeUtil::find_ith(generic_node, i_tree, this->cend()); + } /*find_ith*/ + + BpTreeConstLhs at(Key const & k) const { + const_iterator ix = this->find(k); + + if (ix == this->cend()) { + throw std::out_of_range(tostr("BplusTree::at: expected key argument to appear in tree", + xtag("key", k))); + } + + return BpTreeConstLhs(this, ix.item_addr()); + } /*at*/ + + /* e.g. + * BplusTree bptree = ...; + * Key key = ...; + * BplusTree::value_type x = bptree[key]; + */ + BpTreeConstLhs operator[](Key const & k) const { + const_iterator ix = this->find(k); + + return BpTreeConstLhs(this, ix.item_addr()); + } /*operator[]*/ + + void clear() { + this->n_element_ = 0; + this->leafnode_begin_ = nullptr; + this->leafnode_end_ = nullptr; + this->root_.reset(nullptr); + } /*clear*/ + + /* TODO: + * std::pair insert(value_type const & kv_pair); + * + * template + * std::pair insert(P && value) + * + * std::pair insert(value_type && kv_pair); + * + * iterator insert(iterator pos, value_type const & kv_pair); + * iterator insert(const_iterator pos, value_type const & kv_pair); + * + * template + * iterator insert(const_iterator pos, P && value); + * + * iterator insert(const_iterator pos, value_type && value); + * + * template + * void insert(InputIterator lo, InputIterator hi); + * + * void insert(std::initializer_list initlist); + */ + + /* return: true key already existed (tree size increases by 1) + * false if existing key (tree size unchanged) + */ + std::pair insert(std::pair const & kv_pair) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(this->debug_flag()), + xtag("key", kv_pair.first), + xtag("value", kv_pair.second), + xtag("root", this->root_.get()) + //xtag("nesting", x.nesting_level()) + ); + + log && log(xtag("bptree[before-insert]", (char const *)"...")); + if (log) this->print(std::clog, log.nesting_level()+2); + + std::pair retval; + + if (this->root_) { + NodeType root_type = this->root_->node_type(); + + log && log(xtag("root_type", root_type)); + + switch (root_type) { + case NodeType::leaf: + retval = this->leaf_insert_aux(kv_pair); + break; + case NodeType::internal: + retval = this->internal_insert_aux(kv_pair); + break; + } /*switch*/ + } else { + retval = this->create_root_aux(kv_pair); + } + + log && log(xtag("bptree[after-insert]", (char const *)"...")); + if (log) this->print(std::clog, log.nesting_level() + 2); + + log.end_scope(); + + return retval; + } /*insert*/ + + /* e.g: + * std::map m = ...; + * BplusTree bptree; + * + * bptree.insert(m.begin(), m.end()); + */ + template + void insert(InputIterator lo, InputIterator hi) { + for (InputIterator ix = lo; ix != hi; ++ix) + this->insert(*ix); + } /*insert*/ + + /* return: true if key existed (tree size decreased by 1) + * false if key not found + */ + bool erase(Key const & key) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(this->debug_flag()), + xtag("key", key), + xtag("root", this->root_.get())); + + log && log(xtag("bptree[before-erase]", (char const *)"...")); + if (log) this->print(std::clog, log.nesting_level()+2); + + bool retval = false; + + if (this->root_) { + NodeType root_type = this->root_->node_type(); + + log && log(xtag("root_type", root_type)); + + switch (root_type) { + case NodeType::leaf: + retval = leaf_erase_aux(key); + break; + case NodeType::internal: + retval = internal_erase_aux(key); + break; + } /*switch*/ + } else { + /* tree empty, certainly doesn't contain key */ + } + + log && log(xtag("bptree[after-erase]", (char const *)"...")); + if (log) this->print(std::clog, log.nesting_level()+2); + + log.end_scope(); + + return retval; + } /*erase*/ + + void print(std::ostream & os, std::int32_t indent = 0) const { + using xo::xtag; + using xo::pad; + + os << pad(indent) << "print_aux(os, this->root_.get(), indent+2); + + os << ">"; + os << std::endl; + } /*print*/ + + private: + /* find leaf node associated with given key, within given subtree + * + * .first: index position of leaf in immediate parent of leaf node. 0 when leaf is also root node. + * .seecond: leaf node + */ + static FindNodeResult find_leaf_node_aux(Key const & key, InternalNodeType * subtree_arg) { + FindNodeResult> findresult(0, subtree_arg); + + while (findresult.node() && (findresult.node()->node_type() == NodeType::internal)) { + findresult = (reinterpret_cast(findresult.node()))->find_child(key); + } + + /* findresult.node().node_type() == NodeType::leaf (if non-null) */ + + if (!findresult.node()) { + assert(false); + return FindNodeResult(); + } + + assert(findresult.node()->node_type() == NodeType::leaf); + + /* subtree.canonical_node_type = leaf */ + return FindNodeResult(findresult.ix(), + reinterpret_cast(findresult.node())); + } /*find_leaf_node_aux*/ + + /* count #of keys present in this b+ tree, by visiting every node; + * but short-circuit if internal inconsistency detected + */ + std::size_t verify_helper(bool throw_flag) const { + using xo::scope; + using xo::xtag; + + //scope x("BplusTree.verify_helper"); + + if (Properties::ordinal_tag_value() == tags::ordinal_enabled) { + /* verify tree size (maintained in each node) matches toplevel tree size) */ + if (this->root_ != nullptr) { + if (this->size() != BplusTreeUtil::get_node_size(this->root_.get())) { + if (throw_flag) { + throw std::runtime_error(tostr("BplusTree::verify_helper" + ": mismatched tree size computation", + xtag("root", this->root_.get()), + xtag("bptree.n_element", this->size()), + xtag("bptree.root.size", logutil::nodesize(this->root_.get())))); + } else { + return -1; + } + } + } + } else { + /* subtree size not maintained; skip test */ + } + + /* verify leafnode iterator endpoints */ + + if (this->root_ == nullptr) { + if (this->leafnode_begin_ != nullptr || this->leafnode_end_ != nullptr) { + if (throw_flag) { + throw std::runtime_error(tostr("BplusTree::verify_helper" + ": expected null .leafnode_begin / .leafnode_end pointers" + " with empty tree", + xtag("root", this->root_.get()), + xtag("leafnode_begin", this->leafnode_begin_), + xtag("leafnode_end", this->leafnode_end_))); + } else { + return -1; + } + } + + return 0; + } else { + auto leftmost_fr = this->root_->find_min_leaf_node(); + auto rightmost_fr = this->root_->find_max_leaf_node(); + + if ((leftmost_fr.node() != this->leafnode_begin_) + || (rightmost_fr.node() != this->leafnode_end_)) + { + if (throw_flag) { + throw std::runtime_error(tostr("BplusTree::verify_helper" + ": expected .leafnode_begin / .leafnode_end pointers" + " to match computed first/last leaf nodes", + xtag("root", this->root_.get()), + xtag("leafnode_begin[stored]", this->leafnode_begin_), + xtag("leafnode_begin[computed]", leftmost_fr.node()), + xtag("leafnode_end[stored]", this->leafnode_end_), + xtag("leafnode_end[computed]", rightmost_fr.node()))); + } else { + return -1; + } + } + } + + return this->root_->verify_helper(nullptr /*parent*/, + false /*!with_lub_flag*/, + Key() /*lub_key*/, + nullptr /*lh_leaf*/, + nullptr /*rh_leaf*/); + } /*verify_helper*/ + + /* find leaf node associated with given key; + * this is the node that would contain target key, if it is present. + */ + FindNodeResult find_leaf_node(Key const & key) { + if (!root_.get()) + return FindNodeResult(); + + switch (root_->node_type()) { + case NodeType::leaf: + return FindNodeResult(0, reinterpret_cast(root_.get())); + case NodeType::internal: + return find_leaf_node_aux(key, reinterpret_cast(root_.get())); + } + + assert(false); + return FindNodeResult(); + } /*find_leaf_node*/ + + FindNodeResult find_leaf_node(Key const & key) const { + FindNodeResult findresult = const_cast(this)->find_leaf_node(key); + + return FindNodeResult(findresult.ix(), findresult.node()); + } /*find_leaf_node*/ + + /* insert helper. + * + * require: + * - root node is NodeType::leaf + * - returns true if new key; false if replace value associated with existing key + */ + std::pair + leaf_insert_aux(std::pair const & kv_pair) { + using xo::scope; + using xo::xtag; + + /* will add/replace key,value pair in existing root (which is a leaf) node */ + + scope log(XO_DEBUG(this->debug_flag()), + xtag("key", kv_pair.first), + xtag("value", kv_pair.second)); + + /* root node is a leaf node: + * - tree has between 1 and b elements (where b = branching factor) + */ + LeafNodeType * leaf = reinterpret_cast(this->root_.get()); + + log && log(xtag("leaf", leaf), + xtag("leaf.n_elt", leaf->n_elt()), + xtag("leaf.bf", leaf->branching_factor())); + + /* .elt_v[] + * + * 0 k n-1 with: n <= b = branching factor + * +---+---+- ... -+---+- ... -+---+---+ k = lub(key) in {e1..en} + * | e1| e2| | ek| | | en| + * +---+---+- ... -+---+- ... -+---+---+ + * + * lub_ix_recd.first: true if key already present in tree. implies lub_ix_recd.second >= 1 + * lub_ix_recd.second: upper bound (strict) index position in .elt_v[] of key + */ + std::pair lub_ix_recd = leaf->find_lub_ix(kv_pair.first); + + log && log(xtag("lub_ix_recd.first", lub_ix_recd.first), + xtag("lub_ix_recd.second", lub_ix_recd.second)); + + if (lub_ix_recd.first) { + leaf->assign_leaf_value(lub_ix_recd.second - 1, kv_pair.second); + + return (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf, + lub_ix_recd.second - 1), + false)); + } + + /* key not present in tree, so will be incrementing tree size */ + + if (leaf->n_elt() == leaf->branching_factor()) { + log && log("split root (leaf) node"); + + /* root node is full: + * 1. split into two leaf nodes + * 2. create new root node (internal instead of leaf) + */ + + ++(this->n_element_); + + std::unique_ptr lower(reinterpret_cast(this->root_.release())); + std::unique_ptr upper(lower->split_leaf_upper()); + + /* insert is made into this leaf */ + LeafNodeType * leaf_node = nullptr; + /* new (key, value) pair placed at this index poseition in leaf_node */ + std::size_t leaf_ix = 0; + + /* note corner case: + * when lub_ix_recd.second == lower->n_elt(), + * could either insert (key, value) as smallest key in upper subtree, or largest key in lower subtree. + * however if we put into upper, then also need to correct .root.elt_v_[1].key; + * slightly simpler to insert into lower subtree. + * + */ + if (lub_ix_recd.second <= lower->n_elt()) { + log && log("insert into new LH leaf node"); + leaf_node = lower.get(); + leaf_ix = lub_ix_recd.second; + } else { + log && log("insert into new RH leaf node"); + leaf_node = upper.get(); + leaf_ix = lub_ix_recd.second - lower->n_elt(); + } + + leaf_node->insert_leaf_item(leaf_ix, + std::move(kv_pair), + this->debug_flag()); + + /* create new root node (now with node-type = internal), + * having two child (leaf) nodes + */ + std::unique_ptr root_2 = InternalNodeType::make_2(std::move(lower), + std::move(upper)); + + /* new root node replaces existing root node */ + this->root_ = std::move(root_2); + + this->post_modify_correct_leafnode_endpoints(); + + return (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf_node, + leaf_ix), + true)); + } else if (leaf->n_elt() < this->properties_.branching_factor()) { + /* 1. key is not already present in b+ tree + * 2. leaf_ix+1 is lub on key; move elements [lub .. n_elt) one step to the right + * hope to move elements leaf_ix+1 .. n_elt-1 to the right, + */ + + /* leaf node has room for one more item */ + ++(this->n_element_); + /* insert in root node, moving items [lub_ix .. .n_elt) one to the right */ + leaf->insert_leaf_item(lub_ix_recd.second, + kv_pair, + this->debug_flag()); + + return (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf, + lub_ix_recd.second), + true)); + } else { + /* impossible! */ + + assert(false); + return (std::pair + (const_iterator(), + false)); + } + } /*leaf_insert_aux*/ + + /* insert helper. + * + * require: + * - root node is NodeType::internal + * + * kv_pair: establish assocation kv_pair.first(=key) -> kv_pair.second(=value) + * return: true if insert performed; false if update on existing key + */ + std::pair + internal_insert_aux(std::pair const & kv_pair) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(this->debug_flag()), + xtag("key", kv_pair.first), + xtag("value", kv_pair.second)); + + /* root node is an internal node: + * - tree has at least b elements (where b = branching factor) + */ + FindNodeResult leaffindresult = this->find_leaf_node(kv_pair.first); + LeafNodeType * leaf = leaffindresult.node(); + + log && log(xtag("leaf", leaf), + xtag("leaf.n_elt", leaf->n_elt()), + xtag("leaf.bf", leaf->branching_factor())); + + std::pair lub_ix_recd = leaf->find_lub_ix(kv_pair.first); + + log && log(xtag("lub_ix_recd.first", lub_ix_recd.first), + xtag("lub_ix_recd.second", lub_ix_recd.second)); + + if (lub_ix_recd.first) { + /* key already in tree, just updating associated value */ + leaf->assign_leaf_value(lub_ix_recd.second - 1, kv_pair.second); + + return (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf, + lub_ix_recd.second - 1), + false)); + } + + /* key not present in tree, will be incrementing tree size */ + ++(this->n_element_); + + if (leaf->n_elt() < leaf->branching_factor()) { + log && log("insert into existing leaf, since it has room"); + + /* leaf has room for 1 more item */ + leaf->insert_leaf_item(lub_ix_recd.second, + kv_pair, + this->debug_flag()); + + this->post_modify_add_ancestor_size(leaf->parent(), +1); + + /* whenever we insert at first key position, + * need also to update ancestor glb_key values + */ + { + InternalNodeType * ancestor = leaf->parent(); + + while (ancestor && (kv_pair.first < ancestor->glb_key())) { + ancestor->set_glb_key(kv_pair.first); + ancestor = ancestor->parent(); + } + } + + return (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf, + lub_ix_recd.second), + true)); + } + + /* leaf is full. + * 1. split into two half-full leaf nodes + * 2. insert new (key, value) pair into one of the two + * half-full nodes. + * 3. recursively insert entry for new node into parent; + * possibly splitting parent and additional ancestor + * nodes as need be + */ + + std::unique_ptr new_node; + + /* key,value pair will be inserted into this leaf */ + LeafNodeType * leaf_node = nullptr; + /* key,value pair inserted into leaf at this index position */ + std::size_t leaf_ix = 0; + + if (lub_ix_recd.second < leaf->n_elt() / 2) { + /* will insert into lower_leaf */ + std::unique_ptr lower_leaf(leaf->split_leaf_lower()); + + /* lower_leaf holds lower half of leaf's original set of items. + * leaf now holds upper half of leaf's original set of items. + */ + + log && log("split leaf to get lower_leaf", + xtag("lower_leaf", lower_leaf.get()), + xtag("leaf.n_elt", leaf->n_elt()), + xtag("lower_leaf.n_elt", lower_leaf->n_elt())); + + assert(lub_ix_recd.second <= lower_leaf->n_elt()); + + /* this size temporarily excluded from tree */ + std::size_t decr_z = lower_leaf->size(); + + log && log("insert new key into (new) LH leaf"); + lower_leaf->insert_leaf_item(lub_ix_recd.second, + kv_pair, + this->debug_flag()); + + leaf_node = lower_leaf.get(); + leaf_ix = lub_ix_recd.second; + + new_node = std::move(lower_leaf); + + /* new_node may get attached to ree at non-obvious location. + * at this point it is not in tree. + * + * to bookkeep node sizes, decrement now, then increment where new_node is reintroduced + */ + { + InternalNodeType * parent = leaf->parent(); + + BplusTreeUtil::post_modify_sub_ancestor_size(parent, decr_z, this->debug_flag()); + } + + /* however: leaf's glb increased -> + * need to patch state in (at least parent, possibly more) ancestors + */ + { + GenericNodeType * target = leaf; + InternalNodeType * parent = target->parent(); + + while (parent) { + std::size_t ix = parent->locate_child_by_address(target); + + assert(ix != static_cast(-1)); + + InternalNodeItemType & slot = parent->lookup_elt(ix); + + if (slot.key() == target->glb_key()) { + /* done with fixup */ + break; + } + + slot.set_key(target->glb_key()); + + target = parent; + parent = parent->parent(); + } + } + } else { + /* leaf is full: + * + * note that leaf->n_elt() shrinks across this call + * + * before: + * leaf: + * <-- b elements -> + * 0 1 b-1 + * +---+- ... -+---+ + * | e1| | eb| + * +---+- ... -+---+ + * + * after: + * leaf: upper_leaf: + * <-- b/2 elements -> <-- b/2 elements --> + * 0 1 h 0 + * +---+- ... -+---+ +---+- ... -+---+ + * | e1| | eh| |eh'| | eb| with eh'=e(h+1) + * +---+- ... -+---+ +---+- ... -+---+ + * + * note: if b odd, then: + * - leaf gets (b-1)/2 elements, + * - upper_leaf gets (b+1)/2 elements + */ + + /* will insert into upper_leaf */ + std::unique_ptr upper_leaf(leaf->split_leaf_upper()); + + /* leaf now holds lower half of its original set of items; + * upper holds upper half of leaf's original set of items + */ + + log && log("split leaf to get upper_leaf", + xtag("upper_leaf", upper_leaf.get()), + xtag("leaf.n_elt", leaf->n_elt()), + xtag("upper_leaf.n_elt", upper_leaf->n_elt())); + + assert(lub_ix_recd.second >= leaf->n_elt()); + + /* this size temporarily excluded from tree */ + std::size_t decr_z = upper_leaf->size(); + + log && log("insert new key into (new) RH leaf"); + upper_leaf->insert_leaf_item(lub_ix_recd.second - leaf->n_elt(), + kv_pair, + this->debug_flag()); + + leaf_node = upper_leaf.get(); + leaf_ix = lub_ix_recd.second - leaf->n_elt(); + + new_node = std::move(upper_leaf); + + /* new_node may get attached to tree at non-obvious location. + * at this point it is not in tree. + * + * to bookkeep node sizes, decrement now, then increment where new_node is reintroduced + */ + { + InternalNodeType * parent = leaf->parent(); + + BplusTreeUtil::post_modify_sub_ancestor_size(parent, decr_z, this->debug_flag()); + } + + /* leaf's glb unchanged, no glb fixup required here */ + } + + Key new_key = new_node->glb_key(); + std::size_t lub_ix = 0; + + InternalNodeType * ancestor = leaf->parent(); + + while (ancestor) { + /* invariant: need to add new_node to tree somewhere on path to ancestor. + * new_node is a leaf|internal node with already-correct size, + * that isn't yet accounted for in this B+ tree + */ + + lub_ix = ancestor->find_lub_ix(new_key); + + log && log("fixup ancestors", + xtag("new_key", new_key), + xtag("new_node", new_node.get()), + xtag("new_node.size", logutil::nodesize(new_node.get())), + xtag("ancestor", ancestor), + xtag("lub_ix", lub_ix)); + + /* on this iteration, need to introduce (new_key, new_node) to ancestor */ + + if (ancestor->n_elt() < ancestor->branching_factor()) { + /* ordinal_enabled: #of elements in subtree new_node. + * ordinal_disabled: 0 + */ + std::size_t new_z = BplusTreeUtil::get_node_size(new_node.get()); + + log && log("insert into ancestor, since it has room", + xtag("ancestor.size[pre-insert]", logutil::nodesize(ancestor)), + xtag("new_z", logutil::nodesize(new_node.get()))); + + /* room for 1 more child */ + ancestor->insert_node(lub_ix, + std::move(new_node), + this->debug_flag()); + + /* if ordinal_enabled: increase .size on path from root down to and including ancestor + * otherwise no-op + */ + BplusTreeUtil::post_modify_add_ancestor_size(ancestor, new_z, this->debug_flag()); + this->post_modify_correct_ancestor_glb_keys(ancestor); + this->post_modify_correct_leafnode_endpoints(); + + return (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf_node, + leaf_ix), + true)); + } else { + log && log("pre-split (will split ancestor to make room for new node)", + xtag("ancestor", ancestor), + xtag("ancestor.size", logutil::nodesize(ancestor)), + xtag("new_node", new_node.get()), + xtag("new_node.size", logutil::nodesize(new_node.get()))); + + /* no room in ancestor, need to split */ + + std::unique_ptr upper_ancestor(ancestor->split_internal()); + + log && log("post-split", + xtag("ancestor", ancestor), + xtag("ancestor.size", logutil::nodesize(ancestor)), + xtag("upper_ancestor", upper_ancestor.get()), + xtag("upper_ancestor.size", logutil::nodesize(upper_ancestor.get()))); + + /* this size temporarily excluded from tree */ + std::size_t decr_z = BplusTreeUtil::get_node_size(upper_ancestor.get()); + + /* will add back size fom upper_ancestor (w/ +1 for insert), + * once we figure out where to attach it. + * ancestor.size already decreased via ancestor.split_internal() + */ + BplusTreeUtil::post_modify_sub_ancestor_size(ancestor->parent(), decr_z, this->debug_flag()); + + /* ancestor.n_elt reduced to 1/2 value before call to split_internal() */ + + std::size_t new_z = BplusTreeUtil::get_node_size(new_node.get()); + + if (lub_ix <= ancestor->n_elt()) { + log && log("insert into (existing post-split) LH ancestor"); + log && log(xtag("lub_ix", lub_ix), xtag("new_node", new_node.get()), xtag("new_z", new_z)); + + ancestor->insert_node(lub_ix, + std::move(new_node), + this->debug_flag()); + + /* note: updating entire ancestor chain, + * since next iteration will operate on upper_ancestor != ancestor + */ + BplusTreeUtil::post_modify_add_ancestor_size(ancestor, new_z, this->debug_flag()); + + log && log("LH ancestor size", + xtag("ancestor", ancestor), + xtag("ancestor.size", logutil::nodesize(ancestor))); + + /* note next loop iteration will fixup upper_ancestor. + * upper_ancestor != ancestor + */ + this->post_modify_correct_ancestor_glb_keys(ancestor); + } else { + log && log("insert into (new) RH ancestor"); + log && log(xtag("ix", lub_ix - ancestor->n_elt()), + xtag("new_node", new_node.get()), + xtag("new_z", new_z)); + + upper_ancestor->insert_node(lub_ix - ancestor->n_elt(), + std::move(new_node), + this->debug_flag()); + + /* note: deferring update for ancestor's ancestors until next loop iter */ + BplusTreeUtil::node_add_size(upper_ancestor.get(), new_z); + + log && log("upper ancestor size", + xtag("upper_ancestor", ancestor), + xtag("upper_ancestor.size", logutil::nodesize(ancestor))); + + } + + /* setup for next loop iteration + * reminder: upper_ancestor.size was removed from computed treesize, + * will add back on subsequent iteration (when attaching new_node) + */ + new_key = upper_ancestor->glb_key(); + new_node = std::move(upper_ancestor); + ancestor = ancestor->parent(); + } + } + + log && log("root node was split -> create new root, adding one level"); + + /* if control comes here: + * 1. ancestor is null + * 2. root node was full + has been split. . + * root will become LH subtree of new root + * new_node will become RH subtree of new root + * 3. new_node is not present in .root + */ + + log && log(xtag("root.n_elt", this->root_->n_elt()), + xtag("new_node.n_elt", new_node->n_elt())); + + this->root_ = std::move(InternalNodeType::make_2(std::move(this->root_), + std::move(new_node))); + + this->post_modify_correct_leafnode_endpoints(); + + return (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf_node, + leaf_ix), + true)); + } /*internal_insert_aux*/ + + std::pair + create_root_aux(std::pair const & kv_pair) { + /* create root, with one element */ + this->n_element_ = 1; + + std::unique_ptr leaf_node + = LeafNodeType::make(kv_pair, + this->properties_); + + this->leafnode_begin_ = leaf_node.get(); + this->leafnode_end_ = leaf_node.get(); + + std::pair retval + = (std::pair + (const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + leaf_node.get(), + 0), + true)); + + this->root_.reset(leaf_node.release()); + + return retval; + } /*create_root_aux*/ + + /* remove helper. + * + * require: + * - root node is NodeType::leaf + * - return true iff key found (in which case #of leaf node items decremented) + */ + bool leaf_erase_aux(Key const & key) { + using xo::scope; + using xo::xtag; + + LeafNodeType * leaf = reinterpret_cast(this->root_.get()); + + scope log(XO_DEBUG(this->debug_flag()), + xtag("leaf", leaf), + xtag("leaf.n_elt", leaf->n_elt()), + xtag("leaf.bf", leaf->branching_factor())); + + /* .elt_v[] + * + * 0 k n-1 with: n <= b = branching factor + * +---+---+- ... -+---+- ... -+---+---+ k = lub(key) in {e1..en} + * | e1| e2| | ek| | | en| + * +---+---+- ... -+---+- ... -+---+---+ + * + * lub_ix_recd.first: true if key already present in tree. implies lub_ix_recd.second >= 1 + * lub_ix_recd.second: upper bound (strict) index position in .elt_v[] of key + */ + std::pair lub_ix_recd = leaf->find_lub_ix(key); + + log && log(xtag("lub_ix_recd.first", lub_ix_recd.first), + xtag("lub_ix_recd.second", lub_ix_recd.second)); + + if (!lub_ix_recd.first) { + /* key is not present in tree --> don't modify anything */ + return false; + } + + /* key is present in tree --> will decrement tree size */ + + if (leaf->n_elt() > 1) { + --(this->n_element_); + + /* reminder: lub_ix_recd.second is strict upper bound */ + leaf->remove_leaf(lub_ix_recd.second - 1, + this->debug_flag()); + + } else { + --(this->n_element_); + + /* removed last node -> tree now empty */ + + this->root_.reset(); + + } + + this->post_modify_correct_leafnode_endpoints(); + + log.end_scope(); + + return true; + } /*leaf_erase_aux*/ + + /* remove helper. + * + * require: + * - root node is NodeType::internal + * - return true iff key found (in which case #of key,value pairs decremented) + */ + bool internal_erase_aux(Key const & key) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(this->debug_flag()), + xtag("key", key)); + + std::size_t const bf = this->branching_factor(); + + /* root node is an internal node: + * - tree has at least b elements (where b = branching zfactor) + * + * this + * +------+ + * | | + * +------+ + * . + * . + * +------+ + * | i| i = leaffindresult.ix() + * +------+ + * / | \ + * /-------/ | \--------\ + * | | | + * +------+ +------+ +------+ + * | | | | | j | j = lub_ix_recd.second - 1 + * +------+ +------+ +------+ + * leaf + */ + + FindNodeResult leaffindresult = this->find_leaf_node(key); + LeafNodeType * leaf = leaffindresult.node(); + + log && log(xtag("leaf", leaffindresult.node()), + xtag("leaf.n_elt", leaffindresult.node()->n_elt()), + xtag("leaf.loc", leaffindresult.ix()), + xtag("bf", bf)); + + std::pair lub_ix_recd = leaffindresult.node()->find_lub_ix(key); + + log && log(xtag("lub_ix_recd.first", lub_ix_recd.first), + xtag("lub_ix_recd.second", lub_ix_recd.second)); + + if (!lub_ix_recd.first) { + /* key not in tree */ + return false; + } + + /* key present in tree at leaf.elt_v[lub_ix_recd.second - 1] */ + + /* B+ balance invariant is sustained across remove + * + * if glb key changed, then have to propagate up ancestor chain + */ + --(this->n_element_); + + /* reminder: lub_ix_recd.second is strict upper bound */ + leaf->remove_leaf(lub_ix_recd.second - 1, + this->debug_flag()); + + InternalNodeType * parent = leaf->parent(); + + /* whenever we remove at first key position (with strict upper bound index 1), + * then glb key changed, so need also to update ancestor glb_key values + */ + if (lub_ix_recd.second == 1) { + /* glb_key for this leaf node changed (to larger value) */ + log && log("fix glb", + xtag("@", parent), + xtag("old-glb", parent->glb_key()), + xtag("new-glb", leaf->glb_key())); + + /* we dropped smallest key from [leaf] --> correct glb key for leaf in its immediate parent */ + parent->lookup_elt(leaffindresult.ix()).set_key(leaf->glb_key()); + + this->post_modify_correct_ancestor_glb_keys(parent); + } else { + /* removal from position >0 doesn't change glb key + * -> doesn't require ancestor updates + */ + } + + if (2 * leaf->n_elt() >= bf) { + /* after removal, leaf still has acceptable #of children */ + + /* must decrement tree size on path from root down to and including parent */ + this->post_modify_sub_ancestor_size(parent, +1); + + return true; + } else { + /* after removal, leaf will be too small. plan: + * - try redistributing from a neighboring leaf + * - if result too small, then merge with one of neighboring leaves; + * in this case merges may cascade upward to root + * - if root node shrinks to 1 child, that child becomes new root + */ + log && log("leaf too small after remove -> redistribute or shrink tree"); + + std::size_t leaf_ix = leaffindresult.ix(); + /* right_sibling_ix: position of sibling immediately after (leaf_ix, key), in parent */ + std::size_t right_sibling_ix = leaf_ix + 1; + + LeafNodeType * right_sibling = nullptr; + + if (right_sibling_ix < parent->n_elt()) { + /* consider merge with right sibling */ + right_sibling = reinterpret_cast(parent->lookup_elt(right_sibling_ix).child()); + + std::size_t n = leaf->n_elt() + right_sibling->n_elt(); + + if (n >= 2 * ((bf + 1) / 2)) { + /* can redistribute one or more nodes from right_sibling -> leaf + * e.g. + * if bf=3, require 4 nodes between leaf and rh sibling + * if bf=4, also require 4 nodes between leaf and rh sibling. + * + * after redistribution: + * - leaf will have n/2 elements + * - right_sibling will have n - n/2 elements + */ + leaf->append_from_rh_sibling(n/2 - leaf->n_elt(), right_sibling); + + /* glb_key for right sibling changed, need to fix ancestor book-keeping */ + parent->lookup_elt(right_sibling_ix).set_key(right_sibling->glb_key()); + + this->post_modify_sub_ancestor_size(parent, +1); + this->post_modify_correct_ancestor_glb_keys(parent); + + return true; + } else { + log && log("reject redistrib from right sibling, not enough capacity"); + } + } else { + log && log("reject redistrib from right sibling, doesn't exist"); + } + + std::size_t left_sibling_ix = leaf_ix - 1; + LeafNodeType * left_sibling = nullptr; + + if (leaf_ix > 0) { + /* consider redistribution from left sibling */ + left_sibling = reinterpret_cast(parent->lookup_elt(left_sibling_ix).child()); + + std::size_t n = leaf->n_elt() + left_sibling->n_elt(); + + if (n >= 2 * ((bf + 1) / 2)) { + log && log("redistrib from left sibling"); + + std::size_t n_redistrib = n/2 - leaf->n_elt(); + + log && log(xtag("n/2", n/2), + xtag("leaf.n", leaf->n_elt()), + xtag("n_redistrib", n_redistrib)); + + /* can redistribute one or more nodes from left_sibling -> leaf + * after redistribution: + * - leaf will have n/2 elements + * - left_sibling will have n - n/2 elements + */ + leaf->prepend_from_lh_sibling(left_sibling, n_redistrib, this->debug_flag()); + + /* glb key for leaf changed, need to fix ancestor book-keeping */ + parent->lookup_elt(leaf_ix).set_key(leaf->glb_key()); + + this->post_modify_sub_ancestor_size(parent, +1); + this->post_modify_correct_ancestor_glb_keys(parent); + + return true; + } else { + log && log("reject redistib from left sibling, not enough capacity"); + } + } else { + log && log("reject redistrib from left sibling, doesn't exist"); + } + + /* control here + * -> not enough nodes to redistribute from either sibling + * -> must shrink #nodes in tree + */ + + if (right_sibling_ix < parent->n_elt()) { + assert(right_sibling); + + log && log("merge right sibling"); + + /* RH sibling exists -> merge with it (arbitrary choice if leaf_ix > 0) */ + + leaf->append_rh_sibling(right_sibling); + + /* right_sibling is now (effectively) empty, drop from parent; + * also fixup next_leafnode/prev_leafnode links to bypass + */ + parent->remove_node(right_sibling_ix, this->debug_flag()); + + /* note that glb_key for leaf did not change */ + + /* -1 .size on path from root down to and including parent */ + this->post_modify_sub_ancestor_size(parent, +1); + /* since we reduced #of children at parent, it may have fallen below b/2 lower bound */ + this->post_remove_shrink_ancestor_path(parent); + /* since we removed a leaf node, may have invalidated iterator begin/end endpoints */ + this->post_modify_correct_leafnode_endpoints(); + + return true; + } + + if (leaf_ix > 0) { + assert(left_sibling); + + /* LH sibling exists -> merge with it (arbitrary choice if right_sibling_ix < parent.n_elt */ + + left_sibling->append_rh_sibling(leaf); + + /* leaf is now (effectively) empty, drop from parent; + * also fixup next_leafnode/prev_leafnode links to bypass + */ + parent->remove_node(leaf_ix, this->debug_flag()); + + /* note that glb_key for left_sibling did not change */ + + this->post_modify_sub_ancestor_size(parent, +1); + /* since we reduced #of children at parent, it may have fallen below b/2 lower bound */ + this->post_remove_shrink_ancestor_path(parent); + /* since we removed a leaf node, may have invalidated iterator begin/end endpoints */ + this->post_modify_correct_leafnode_endpoints(); + + return true; + } + + /* must have at least one sibling (else prior visit would have shrunk tree height) */ + } + + log.end_scope(); + + assert(false); + return false; + } /*internal_erase_aux*/ + + void post_modify_add_ancestor_size(InternalNodeType * parent, std::size_t incr_z) { + BplusTreeUtil::post_modify_add_ancestor_size(parent, incr_z, this->debug_flag()); + } /*post_modify_add_ancestor_size*/ + + void post_modify_sub_ancestor_size(InternalNodeType * parent, std::size_t decr_z) { + BplusTreeUtil::post_modify_sub_ancestor_size(parent, decr_z, this->debug_flag()); + } /*post_modify_sub_ancestor_size*/ + + void post_modify_correct_ancestor_glb_keys(InternalNodeType * parent) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(this->debug_flag()), + xtag("parent", parent)); + + InternalNodeType * grandparent = parent->parent(); + + std::size_t i_ancestor = 0;; + while (grandparent) { + log && log(xtag("i_ancestor", i_ancestor), + xtag("grandparent", grandparent)); + + /* find index position of parent subtree, as child of grandparent + * Can only use .find_ix() when key-invariants are satisfied. + * + * Warning: O(bf) call here + */ + std::size_t parent_ix = grandparent->locate_child_by_address(parent); + + log && log(xtag("parent.loc", parent_ix)); + + if (grandparent->lookup_elt(parent_ix).key() == parent->glb_key()) { + log && log("grandparent[parent.loc].key == parent.glb_key --> done"); + break; + } + + log && log("fix glb key in grandparent"); + grandparent->lookup_elt(parent_ix).set_key(parent->glb_key()); + + /* + repeat 1 level up.. */ + parent = grandparent; + grandparent = parent->parent(); + } + } /*post_modify_correct_ancestor_glb_keys*/ + + /* reset .leafnode_begin, .leafnode_end after changing the set of nodes in a b+ tree */ + void post_modify_correct_leafnode_endpoints() { + if (root_) { + this->leafnode_begin_ = root_->find_min_leaf_node().node(); + this->leafnode_end_ = root_->find_max_leaf_node().node(); + } else { + this->leafnode_begin_ = nullptr; + this->leafnode_end_ = nullptr; + } + } /*post_modify_correct_leafnode_endpoints*/ + + void post_remove_shrink_ancestor_path(InternalNodeType * node) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(this->debug_flag())); + + std::size_t const bf = node->branching_factor(); + + while (node + && (node != this->root_.get())) { + + log && log(xtag("node", node), + xtag("node.n_elt", node->n_elt())); + + if (2 * node->n_elt() >= bf) + break; + + /* node has fewer children than B+ minimum. + * either: + * - redistribute nodes from sibling + * (so that merged node satisfies bf/2 <= n <= bf) + * - merge with sibling + */ + InternalNodeType * parent = node->parent(); + + /* O(bf), but doesn't rely on satisfied key invariants */ + std::size_t node_ix = parent->locate_child_by_address(node); + std::size_t right_sibling_ix = node_ix + 1; + + InternalNodeType * right_sibling = nullptr; + + if (right_sibling_ix < parent->n_elt()) { + /* consider redistributng from right sibling */ + right_sibling = reinterpret_cast(parent->lookup_elt(right_sibling_ix).child()); + + std::size_t n = node->n_elt() + right_sibling->n_elt(); + + if (n >= 2 * ((bf + 1) / 2)) { + log && log("redistribute from right_sibling", + xtag("lh.n", node->n_elt()), + xtag("rh.n", right_sibling->n_elt())); + + /* can redistribute one or more nodes from right_sibling -> node + * + * after redistribution: + * - node will have floor(n/2) elements + * - right_sibling will have ceil(n/2) = n - floor(n/2) elements + */ + node->append_from_rh_sibling(n/2 - node->n_elt(), right_sibling); + + /* glb_key for right sibling changed, need to fixup ancestor book-keeping */ + this->post_modify_correct_ancestor_glb_keys(right_sibling); + + return; + } + } + + std::size_t left_sibling_ix = node_ix - 1; /* but beware underflow when node_ix=0 */ + + InternalNodeType * left_sibling = nullptr; + + if (node_ix > 0) { + /* consider redistributing from left sibling */ + left_sibling = reinterpret_cast(parent->lookup_elt(left_sibling_ix).child()); + + std::size_t n = node->n_elt() + left_sibling->n_elt(); + + if (n >= 2 * ((bf + 1) / 2)) { + log && log("redistribute from left_sibling", + xtag("lh.n", left_sibling->n_elt()), + xtag("rh.n", node->n_elt())); + + /* redistribute one or more nodes from left_sibling -> node */ + node->prepend_from_lh_sibling(left_sibling, + n/2 - node->n_elt(), + this->debug_flag()); + + /* glb_key for node changed, need to fixup ancestor book-keeping */ + this->post_modify_correct_ancestor_glb_keys(node); + + return; + } + } + + log && log("cannot redistribute -> drop a node"); + + /* control here + * -> not enough nodes to redistribute from either sibling + * -> must shrink number of nodes in tree + */ + + if (right_sibling_ix < parent->n_elt()) { + assert(right_sibling); + + /* RH sibling exists -> merge with it */ + + node->append_rh_sibling(right_sibling); + + /* right sibling now empty, drop from parent */ + parent->remove_node(right_sibling_ix, this->debug_flag()); + } else if (node_ix > 0) { + assert(left_sibling); + + /* LH sibling exists -> merge with it */ + + left_sibling->append_rh_sibling(node); + + /* node is now empty, drop from parent */ + parent->remove_node(node_ix, this->debug_flag()); + } + + /* continue tree fixup at parent node */ + node = parent; + } + + /* if node != root: tree shrank successfully, without propgating to root */ + + if ((node == this->root_.get()) && (node->n_elt() == 1)) + { + /* replace root with its single child element; tree height shrinks by one */ + this->root_ = std::move(node->lookup_elt(0).release_child()); + + this->root_->set_parent(nullptr); + } + } /*post_remove_shrink_ancestor_path*/ + + void print_aux(std::ostream & os, + GenericNodeType const * node, + std::uint32_t indent) const + { + using xo::xtag; + + if (node) { + switch(node->node_type()) { + case NodeType::internal: + { + using xo::pad; + + InternalNodeType const * internal = reinterpret_cast(node); + + for (std::uint32_t i=0, n=internal->n_elt(); ilookup_elt(i).child()->n_elt()) + << xtag("treez", logutil::nodesize(internal->lookup_elt(i).child())) + << xtag("glb", internal->lookup_elt(i).key()) + << xtag("@", internal->lookup_elt(i).child()); + + this->print_aux(os, + internal->lookup_elt(i).child(), + indent+1); + } + } + break; + case NodeType::leaf: + { + using xo::pad; + + LeafNodeType const * leaf = reinterpret_cast(node); + + for (std::uint32_t i=0, n=leaf->n_elt(); ilookup_elt(i).key() + << ": " << leaf->lookup_elt(i).value(); + } + } + break; + } + } else { + //os << std::endl; + } + } /*print_aux*/ + + private: + /* tree properties, in particular: branching factor */ + Properties properties_; + + /* #of items in this tree */ + std::size_t n_element_ = 0; + + /* left-most leaf node for inorder traversal */ + LeafNodeType * leafnode_begin_ = nullptr; + /* right-most leaf node for inorder traversal */ + LeafNodeType * leafnode_end_ = nullptr; + + /* tree + * size root depth + * ------------------------- + * 0 nullptr 0 + * 1..b LeafNode 1 + * >b InternalNode >1 + */ + std::unique_ptr root_; + }; /*BplusTree*/ + + } /*namespace tree*/ +} /*namespace xo*/ + +/* end BplusTree.hpp */ diff --git a/include/xo/tree/RedBlackTree.hpp b/include/xo/tree/RedBlackTree.hpp new file mode 100644 index 00000000..e9460b95 --- /dev/null +++ b/include/xo/tree/RedBlackTree.hpp @@ -0,0 +1,3158 @@ +/* @file RedBlackTree.hpp */ + +/* provides red-black tree with order statistics. + */ + +#pragma once + +#include "indentlog/scope.hpp" +#include "indentlog/print/pad.hpp" +#include "indentlog/print/quoted.hpp" +#include +#include +#include +#include +#include +#include + +namespace xo { + namespace tree { + + /* concept for the 'Reduce' argument to RedBlackTree<...> + * + * here: + * T = class implementing reduce feature, e.g. SumReduce<...> + * T::value_type = type for output of reduce function. + * + * Value = value_type for rb-tree that supports ordinal statistics + * + * e.g. + * struct ReduceCountAndSum { + * using value_type = std::pair: + * + * value_type nil() { return value_type(0, 0); } + * value_type operator()(value_type const & acc, int64_t val) + * { return value_type(acc.first + val.first, acc.second + val.second); } + * value_type operator()(value_type const & a1, value_type const & a2) + * { return value_type(a1.first + a2.first, a1.second + a2.second); } + * }; + * + * Reduce.nil() -> nominal reduction i.e. reduce on empty set + * Reduce.leaf(v) -> reduction on set {v} + * + * in general: at some internal node, tree splits set of key/value pairs on some key k1, + * with a left subtree lh, and a right subtree rh. + * + * for a binary tree we want to maintain: + * - r1: reduce applied to collection + * lh + {k1} = reduce(reduce(lh), k1) + * - r2: reduce applied to collection + * lh + {k1} + rh = reduce.combine(r1, reduce(r2)) + * + */ + template + concept ReduceConcept = requires(T r, Value v, typename T::value_type a) { + typename T::value_type; + { r.nil() } -> std::same_as; + { r.leaf(v) } -> std::same_as; + { r(a, v) } -> std::same_as; + { r.combine(a, a) } -> std::same_as; + }; + + /* reduce function that disappears at compile time */ + template + struct NullReduce; + + /* red-black tree with order statistics + * + * require: + * - Key is equality comparable + * - Key, Value, Reduce are copyable and null-constructible + * - Reduce.value_type = Accumulator + * - Reduce.operator() :: (Accumulator x Key) -> Accumulator + * - Reduce.operator() :: (Accumulator x Accumulator) -> Accumulator + */ + template > + class RedBlackTree; + + namespace detail { + enum Color { C_Invalid = -1, C_Black, C_Red, N_Color }; + + enum Direction { D_Invalid = -1, D_Left, D_Right, N_Direction }; + + inline Direction other(Direction d) { + return static_cast(1 - d); + } /*other*/ + + template + class RbTreeUtil; + + /* xo::tree::detail::Node + * + * Require: + * - Key.operator< + * - Key.operator== + * + */ + template + class Node { + public: + using ReducedValue = typename Reduce::value_type; + using ContentsType = std::pair; + using value_type = std::pair; + + public: + Node() = default; + Node(value_type const & kv_pair, + std::pair const & r) + : color_(C_Red), size_(1), contents_{kv_pair}, reduced_(r) {} + Node(value_type && kv_pair, + std::pair && r) + : color_(C_Red), size_(1), + contents_{std::move(kv_pair)}, + reduced_{std::move(r)} {} + + static Node * make_leaf(value_type const & kv_pair, + ReducedValue const & leaf_rv) { + return new Node(kv_pair, + std::pair(leaf_rv, leaf_rv)); + } /*make_leaf*/ + + static Node * make_leaf(value_type && kv_pair, + ReducedValue const & leaf_rv) { + return new Node(kv_pair, + std::pair(leaf_rv, leaf_rv)); + } /*make_leaf*/ + + /* return #of key/vaue pairs in tree rooted at x. */ + static size_t tree_size(Node *x) { + if (x) + return x->size(); + else + return 0; + } /*tree_size*/ + + static bool is_black(Node *x) { + if (x) + return x->is_black(); + else + return true; + } /*is_black*/ + + static bool is_red(Node *x) { + if (x) + return x->is_red(); + else + return false; + } /*is_red*/ + + static Direction child_direction(Node *p, Node *n) { + if (p) { + return p->child_direction(n); + } else { + return D_Invalid; + } + } /*child_direction*/ + + static ReducedValue reduce_aux(Reduce reduce, Node *x) + { + if(x) + return x->reduced2(); + else + return reduce.nil(); + } /*reduce_aux*/ + + /* calculate reduced values for node x. + * does not used x.reduced + */ + static std::pair reduced_pair(Reduce r, Node const * x) + { + if(!x) + assert(false); + + ReducedValue r1 = r(reduce_aux(r, x->left_child()), + x->value()); + ReducedValue r2 = r.combine(r1, + reduce_aux(r, x->right_child())); + return std::pair(r1, r2); + } /*reduced_pair*/ + + /* replace root pointer *pp_root with x; + * set x parent pointer to nil + */ + static void replace_root_reparent(Node *x, Node **pp_root) { + *pp_root = x; + if (x) + x->parent_ = nullptr; + } /*replace_root_reparent*/ + + size_t size() const { return size_; } + /* const access */ + ContentsType const & contents() const { return contents_; } + /* non-const value access. + * + * editorial: would prefer to return + * std::pair & + * here, so that tree[k].first = newk + * prohibited, but std::pair + * is considered unrelated to std::pair, + * so l-value conversion not allowed + */ + ContentsType & contents() { return contents_; } + + Node *parent() const { return parent_; } + Node *child(Direction d) const { return child_v_[d]; } + Node *left_child() const { return child_v_[0]; } + Node *right_child() const { return child_v_[1]; } + ReducedValue const & reduced1() const { return reduced_.first; } + ReducedValue const & reduced2() const { return reduced_.second; } + + /* true if this node has 0 children */ + bool is_leaf() const { + return ((child_v_[0] == nullptr) && (child_v_[1] == nullptr)); + } + + /* identify which child x represents + * Require: + * - x != nullptr + * - x is either this->left_child() or this->right_child() + */ + Direction child_direction(Node *x) { + if (x == this->left_child()) + return D_Left; + else if (x == this->right_child()) + return D_Right; + else + return D_Invalid; + } /*child_direction*/ + + bool is_black() const { return this->color_ == C_Black; } + bool is_red() const { return this->color_ == C_Red; } + + bool is_red_left() const { return is_red(this->left_child()); } + bool is_red_right() const { return is_red(this->right_child()); } + + /* true if this node is red, and either child is red */ + bool is_red_violation() const { + if (this->color_ == C_Red) { + Node *left = this->left_child(); + Node *right = this->right_child(); + + if (left && left->is_red()) + return true; + + if (right && right->is_red()) + return true; + } + + return false; + } /*is_red_violation*/ + + Color color() const { return color_; } + Key const & key() const { return contents_.first; } + Value const & value() const { return contents_.second; } + + /* recalculate size from immediate childrens' sizes + * editor bait: recalc_local_size() + */ + void local_recalc_size(Reduce const & reduce_fn) { + using xo::scope; + using xo::xtag; + + //constexpr char const * c_self = "Node::local_recalc_size"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + this->size_ = (1 + + Node::tree_size(this->left_child()) + + Node::tree_size(this->right_child())); + + /* (note: want reduce applied to all of left subtree) */ + this->reduced_ = Node::reduced_pair(reduce_fn, this); + + log && log("done recalc for key k, value v, reduced r", + xtag("k", this->key()), + xtag("v", this->value()), + xtag("r1", this->reduced1()), + xtag("r2", this->reduced2())); + } /*local_recalc_size*/ + + private: + void assign_color(Color x) { this->color_ = x; } + void assign_size(size_t z) { this->size_ = z; } + + void assign_child_reparent(Direction d, Node *new_x) { + Node *old_x = this->child_v_[d]; + + // trying to fix old_x can be counterproductive, + // since old_x->parent_ may already have been corrected, + // + if (old_x && (old_x->parent_ == this)) + old_x->parent_ = nullptr; + + this->child_v_[d] = new_x; + + if (new_x) { + new_x->parent_ = this; + } + } /*assign_child_reparent*/ + + /* replace child that points to x, with child that points to x_new + * and return direction of the child that was replaced + * + * Require: + * - x is a child of *this + * - x_new is not a child of *this + * + * promise: + * - x is nullptr or x.parent is nullptr + * - x_new is nullptr or x_new.parent is this + */ + Direction replace_child_reparent(Node *x, Node *x_new) { + Direction d = this->child_direction(x); + + if (d == D_Left || d == D_Right) { + this->assign_child_reparent(d, x_new); + return d; + } else { + return D_Invalid; + } + } /*replace_child_reparent*/ + + friend class RbTreeUtil; + friend class xo::tree::RedBlackTree; + + private: + /* red | black */ + Color color_ = C_Red; + /* size of subtree (#of key/value pairs) rooted at this node */ + size_t size_ = 0; + /* .first = key associated with this node + * .second = value associated with this node + * .third = reduced value + */ + ContentsType contents_; + /* accumulator for some binary function of Values. + * must be associative, since value will be produced + * by any testing of calls to Reduce::combine(). + * + * e.g. {a, b, c, d} could be reduced by: + * r(r(a,b), r(c,d)) + * or + * r(a, r(r(b, c), d)) + * etc. + * + * examples: + * - count #of keys + * - sum key values + * + * .reduced.first: reduce applied to all values with keys <= .contents.first + * .reduced.second: reduce applied to all values in this subtree. + */ + std::pair reduced_; + /* pointer to parent node, nullptr iff this is the root node */ + Node *parent_ = nullptr; + /* + * .child_v[0] = left child + * .child_v[1] = right child + * + * invariants: + * - if .child_v[x] non-null, then .child_v[0]->parent = this + * - a red node may not have red children + */ + std::array child_v_ = {nullptr, nullptr}; + }; /*Node*/ + + enum IteratorDirection { + /* ID_Forward. forward iterator + * ID_Reverse. reverse iterator + */ + ID_Forward, + ID_Reverse + }; /*IteratorDirection*/ + + /* specify iterator location relative to Iterator::node. + * using this to make it possible to correctly decrement an + * iterator at RedBlackTree::end(). + * + * IL_BeforeBegin. if non-empty tree, .node is the first node + * in the tree (the one with smallest key), + * and iterator refers to the location + * "one before" that first node. + * IL_Regular. iterator refers to member of the tree + * given by Iterator::node + * IL_AfterEnd. if non-empty tree, .node is the last node + * in the tree (the one with largest key), + * and iterator refers the the location + * "one after" that last node. + */ + enum IteratorLocation { + IL_BeforeBegin, + IL_Regular, + IL_AfterEnd, + }; /*IteratorLocation*/ + + /* require: + * - Reduce::value_type + */ + template + class RbTreeUtil { + public: + using RbNode = Node; + using ReducedValue = typename Reduce::value_type; + using value_type = std::pair; + + public: + /* return #of key/vaue pairs in tree rooted at x. */ + static size_t tree_size(RbNode *x) { + if (x) + return x->size(); + else + return 0; + } /*tree_size*/ + + static bool is_black(RbNode *x) { + if (x) + return x->is_black(); + else + return true; + } /*is_black*/ + + static bool is_red(RbNode *x) { + if (x) + return x->is_red(); + else + return false; + } /*is_red*/ + + /* for every node n in tree, call fn(n, d'). + * d' is the depth of the node n relative to starting point x, + * not counting red nodes. + * make calls in increasing key order (i.e. inorder traversal) + * argument d is the black-height of tree above x + * + * Require: + * - fn(x, d) + */ + template + static void inorder_node_visitor(RbNode const * x, uint32_t d, Fn && fn) { + if (x) { + /* dd: black depth of child subtrees*/ + uint32_t dd = (x->is_black() ? d + 1 : d); + + inorder_node_visitor(x->left_child(), dd, fn); + /* dd includes this node */ + fn(x, dd); + inorder_node_visitor(x->right_child(), dd, fn); + } + } /*inorder_node_visitor*/ + + /* note: RedBlackTree.clear() abuses this to visit-and-delete + * all nodes + */ + template + static void postorder_node_visitor(RbNode const * x, uint32_t d, Fn && fn) { + if (x) { + uint32_t dd = (x->is_black() ? d + 1 : d); + + postorder_node_visitor(x->left_child(), dd, fn); + postorder_node_visitor(x->right_child(), dd, fn); + /* dd includes this node */ + fn(x, dd); + } + } /*postorder_node_visitor*/ + + /* return the i'th inorder node (counting from 0) + * belonging to the subtree rooted at N. + * + * behavior not defined if subtree at N contains less than + * (i + 1) nodes + */ + static RbNode * find_ith(RbNode * N, uint32_t i) { + if(!N) + return nullptr; + + RbNode * L = N->left_child(); + uint32_t n_left = tree_size(L); + + if(i < n_left) + return find_ith(L, i); + else if(i == n_left) + return N; + else if(i < N->size_) + return find_ith(N->right_child(), i - (n_left + 1)); + else + return nullptr; + } /*find_ith*/ + + /* starting from x, traverse only left children + * to find node with a nil left child. + * + * This node has the smallest key in subtree N + */ + static RbNode * find_leftmost(RbNode * N) { + while(N) { + RbNode * S = N->left_child(); + + if(!S) + break; + + N = S; + } + + return N; + } /*find_leftmost*/ + + /* return node containing the next key after N->key_ in the tree + * containing N. This will be either a descendant of N, + * or an ancestor of N. + * returns nil if x.key is the largest key in tree containing x. + */ + static RbNode * next_inorder_node(RbNode * N) { + if(!N) + return nullptr; + + if(N->right_child()) + return find_leftmost(N->right_child()); + + /* N has no right child --> + * successor is the nearest ancestor with a left child + * on path to N + */ + + RbNode * x = N; + + while(x) { + RbNode * P = x->parent(); + + if(P && P->left_child() == x) { + return P; + } + + /* path P..N traverses only right-child pointers) */ + x = P; + } + + /* no ancestor of N with a left child, so N has the largest key + * in the tree + */ + return nullptr; + } /*next_inorder_node*/ + + /* return node containing the key before N->key_ in the tree containing N. + * This will be either a descendant of N, or an ancestor of N + */ + static RbNode * prev_inorder_node(RbNode * N) { + if(!N) + return nullptr; + + if(N->left_child()) + return find_rightmost(N->left_child()); + + /* N has no left child --> + * predecessor is the nearest ancestor with a right child + * on path to N + */ + + RbNode * x = N; + + while(x) { + RbNode * P = x->parent(); + + if(P && (P->right_child() == x)) { + return P; + } + + /* path P..N traverses only left-child pointers */ + x = P; + } + + /* no ancestor of N with a right child, so N has the smallest key + * in tree that containing it. + */ + return nullptr; + } /*prev_inorder_node*/ + + /* compute value of reduce applied to the set K of all keys k[j] in subtree N + * with: + * k[j] <= lub_key if is_closed = true + * k[j] < lub_key if is_closed = false + * return reduce_fn.nil() if K is empty + */ + static ReducedValue reduce_lub(Key const & lub_key, + Reduce const & reduce_fn, + bool is_closed, + RbNode * N) + { + ReducedValue retval = reduce_fn.nil(); + + for (;;) { + if (!N) + return retval; + + if ((N->key() < lub_key) || (is_closed && (N->key() == lub_key))) { + /* all keys k[i] in left subtree of N satisfy k[i] < lub_key + * apply reduce to: + * - left subtree of N + * - N->key() depending on comparison with lub_key + * - any members of right subtree of N, with key < lub_key; + */ + retval = reduce_fn.combine(retval, N->reduced1()); + N = N->right_child(); + } else { + /* all keys k[j] in right subtree of N do NOT satisfy k[j] < + * lub_key, exclude these. also exclude N->key() + */ + N = N->left_child(); + } + } + } /*reduce_lub*/ + + /* find largest key k such that + * reduce({node j in subtree(N)) | j.key <= k}) < p + * + * ^ + * 1 | xxxx + * | xx + * p |....... x + * | x + * | xx . + * | xxxx . + * 0 +----------------> + * ^ + * find_cum_glb(p) + * + * here Key is a sample value, + * Value counts #of samples with that key. + * + * find_cum_glb() computes inverse for a monotonically increasing function, + * if reduce(S) = sum {j.value | j in S}; + * + * if rbtree stores values for a discrete function f: IR -> IR+, + * then x = find_sum_glb(p)->key() inverts the integral of f, i.e. + * computes: + * x + * / + * | + * sup { x: | f(z) dz < y } + * | + * / + * -oo + * + * Require: + * - Reduce behaves like sum: + * must deliver monotonically increasing values + * with increasing key-values. + * + * (for example: if Value is non-negative and Reduce is SumReduce) + */ + static RbNode * find_sum_glb(Reduce const & reduce_fn, + RbNode * N, + typename Reduce::value_type y) { + using xo::scope; + using xo::xtag; + + constexpr char const * c_self = "RbTreeUtil::find_sum_glb"; + constexpr bool c_logging_enabled = false; + scope log(XO_DEBUG(c_logging_enabled)); + + if(!N) { + log && log(c_self, ": return nullptr"); + return nullptr; + } + + typename Reduce::value_type left_sum + = RbNode::reduce_aux(reduce_fn, N->left_child()); + typename Reduce::value_type right_sum + = RbNode::reduce_aux(reduce_fn, N->right_child()); + + log && log("with", + xtag("y", y), + xtag("N.key", N->key()), + xtag("N.value", N->value()), + xtag("N.reduced1", N->reduced1()), + xtag("left_sum", left_sum), + xtag("right_sum", right_sum)); + + if (y <= left_sum) { + return find_sum_glb(reduce_fn, N->left_child(), y); + } else if (y <= N->reduced1() || !N->right_child()) { + log && log("return N"); + /* since N.reduced = reduce(left_sum, N.value, right_sum) */ + return N; + } else { + /* find bound in non-null right subtree */ + return find_sum_glb(reduce_fn, N->right_child(), y - N->reduced1()); + } + } /*find_sum_glb*/ + + /* starting from x, traverse only right children + * to find node with a nil right child + * + * This node has the largest key in subtree N + */ + static RbNode * find_rightmost(RbNode *N) { + while(N) { + RbNode *S = N->right_child(); + + if (!S) + break; + + N = S; + } + + return N; + } /*find_rightmost*/ + + /* find node in x with key k + * return nullptr iff no such node exists. + */ + static RbNode * find(RbNode * x, Key const & k) { + for (;;) { + if (!x) + return nullptr; + + if (k < x->key()) { + /* search in left subtree */ + x = x->left_child(); + } else if (k == x->key()) { + return x; + } else /* k > x->key() */ { + x = x->right_child(); + } + } + } /*find*/ + + /* find greatest lower bound for key k in tree x, + * provided it's tighter than candidate h. + * + * require: + * if h is provided, then x belongs to right subtree of h + * (so any key k' in x satisfies k' > h->key) + * + */ + static RbNode *find_glb_aux(RbNode *x, RbNode *h, Key const &k, + bool is_closed) { + for (;;) { + if (!x) + return h; + + if (x->key() < k) { + /* x.key is a lower bound for k */ + + if (x->right_child() == nullptr) { + /* no tighter lower bounds present in subtree rooted at x */ + + /* x must be better lower bound than h, + * since when h is non-nil we are searching right subtree of h + */ + return x; + } + + /* look for better lower bound in right child */ + h = x; + x = x->right_child(); + continue; + } else if (is_closed && (x->key() == k)) { + /* x.key is exact match */ + return x; + } else { + /* x.key is an upper bound for k. If there's a lower bound, + * it must be in left subtree of x + */ + + /* preserving h */ + x = x->left_child(); + continue; + } + } /*looping over tree nodes*/ + } /*find_glb_aux*/ + + /* find greatest lower bound node for a key, in this subtree + * + * is_open. if true, allow result with N->key = k exactly + * if false, require N->key < k + */ + static RbNode * find_glb(RbNode * x, Key const & k, bool is_closed) { + return find_glb_aux(x, nullptr, k, is_closed); + } /*find_glb*/ + +#ifdef NOT_IN_USE + /* find least upper bound node for a key, in this subtree* + * + * is_open. if true, allow result with N->key = k exactly + * if false, require N->key > k + */ + static RbNode *find_lub(RbNode *x, Key const &k, bool is_closed) { + if (x->key() > k) { + /* x.key is an upper bound for k */ + if (x->left_child() == nullptr) { + /* no tigher upper bound present in subtree rooted at x */ + return x; + } + + RbNode *y = find_lub(x->left_child(), k, is_closed); + + if (y) { + /* found better upper bound in left subtree */ + return y; + } else { + return x; + } + } else if (is_closed && (x->key() == k)) { + return x; + } else { + /* x.key is not an upper bound for k */ + return find_lub(x->right_child(), k, is_closed); + } + } /*find_lub*/ +#endif + + /* perform a tree rotation in direction d at node A. + * + * Require: + * - A is non-nil + * - A->child(other(d)) is non-nil + * + * if direction=D_Left: + * + * G G + * | | + * A B <- retval + * / \ / \ + * R B ==> A T + * / \ / \ + * S T R S + * + * if direction=D_Right: + * + * G G + * | | + * A B <- retval + * / \ / \ + * B R ==> T A + * / \ / \ + * T S S R + */ + static RbNode *rotate(Direction d, RbNode *A, + Reduce const & reduce_fn, + RbNode **pp_root) { + using xo::scope; + using xo::xtag; + + //constexpr char const *c_self = "RbTreeUtil::rotate"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + Direction other_d = other(d); + + RbNode *G = A->parent(); + RbNode *B = A->child(other_d); + //RbNode *R = A->child(d); // not using + RbNode *S = B->child(d); + //RbNode *T = B->child(other_d); // not using + + if (log.enabled()) { + log("rotate-", (d == D_Left) ? "left" : "right", + " at", xtag("A", A), xtag("A.key", A->key()), xtag("B", B), + xtag("B.key", B->key())); + + if (G) { + log("with G", xtag("G", G), + xtag("G.key", G->key())); + // display_aux(D_Invalid /*side*/, G, 0, &lscope); + } else { + log("with A at root"); + // display_aux(D_Invalid /*side*/, A, 0, &lscope); + } + } + + /* note: this will set A's old child B to have null parent ptr */ + A->assign_child_reparent(other_d, S); + A->local_recalc_size(reduce_fn); + + B->assign_child_reparent(d, A); + B->local_recalc_size(reduce_fn); + + if (G) { + G->replace_child_reparent(A, B); + assert(B->parent() == G); + + /* note: G.size not affected by rotation */ + } else { + RbNode::replace_root_reparent(B, pp_root); + } + + return B; + } /*rotate*/ + + /* fixup size in N and all ancestors of N, + * after insert/remove affecting N + */ + static void fixup_ancestor_size(Reduce const & reduce_fn, RbNode *N) { + while (N) { + N->local_recalc_size(reduce_fn); + N = N->parent(); + } + } /*fixup_ancestor_size*/ + + /* rebalance to fix possible red-red violation at node G or G->child(d). + * + * diagrams are for d=D_Left; + * mirror left-to-right to get diagram for d=D_Right + * + * G + * d-> / \ <-other_d + * P U + * / \ + * R S + * + * relative to prevailing black-height h: + * - P at h + * - U at h + * - may have red-red violation between G and P + * + * Require: + * - tree is in RB-shape, except for possible red-red violation + * between {G,P} or {P,R|S} + * Promise: + * - tree is in RB-shape + */ + static void fixup_red_shape(Direction d, RbNode *G, + Reduce const & reduce_fn, + RbNode **pp_root) { + using xo::scope; + using xo::xtag; + using xo::print::ccs; + + //constexpr char const *c_self = "RbTreeUtil::fixup_red_shape"; + constexpr bool c_logging_enabled = false; + constexpr bool c_excessive_verify_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + RbNode *P = G->child(d); + + for (uint32_t iter = 0;; ++iter) { + if (c_excessive_verify_enabled) + RbTreeUtil::verify_subtree_ok(reduce_fn, G, nullptr /*&black_height*/); + + if (log.enabled()) { + if (G) { + log("consider node G with d-child P", + xtag("iter", iter), xtag("G", G), + xtag("G.col", ccs((G->color() == C_Red) ? "r" : "B")), + xtag("G.key", G->key()), + xtag("d", ccs((d == D_Left) ? "L" : "R")), + xtag("P", P), + xtag("P.col", ccs((P->color() == C_Red) ? "r" : "B")), + xtag("P.key", P->key())); + } else { + log("consider root P", xtag("iter", iter), + xtag("P", P), + xtag("P.col", ccs((P->color() == C_Red) ? "r" : "B")), + xtag("P.key", P->key())); + } + + RbTreeUtil::display_aux(D_Invalid /*side*/, G ? G : P, 0 /*d*/, + &log); + } /*if logging enabled*/ + + if (G && G->is_red_violation()) { + log && log("red-red violation at G - defer"); + + /* need to fix red-red violation at next level up + * + * . (=G') + * | (=d') + * G* (=P') + * d-> / \ <-other-d + * P* U + * / \ + * R S + */ + P = G; + G = G->parent(); + d = RbNode::child_direction(G, P); + + continue; + } + + log && log("check for red violation at P"); + + if (!P->is_red_violation()) { + log && log("red-shape ok at {G,P}"); + + /* RB-shape restored */ + return; + } + + if (!G) { + log && log("make P black to fix red-shape at root"); + + /* special case: P is root of tree. + * can fix red violation by making P black + */ + P->assign_color(C_Black); + return; + } + + Direction other_d = other(d); + + RbNode *R = P->child(d); + RbNode *S = P->child(other_d); + RbNode *U = G->child(other_d); + + if (log.enabled()) { + log("got R,S,U", xtag("R", R), xtag("S", S), + xtag("U", U)); + if (R) { + log("with", + xtag("R.col", ccs(R->color_ == C_Black ? "B" : "r")), + xtag("R.key", R->key())); + } + if (S) { + log("with", + xtag("S.col", ccs(S->color_ == C_Black ? "B" : "r")), + xtag("S.key", S->key())); + } + if (U) { + log("with", + xtag("U.col", ccs(U->color_ == C_Black ? "B" : "r")), + xtag("U.key", U->key())); + } + } + + assert(is_black(G)); + assert(is_red(P)); + assert(is_red(R) || is_red(S)); + + if (RbNode::is_red(U)) { + /* if d=D_Left: + * + * *=red node + * + * . . (=G') + * | | (=d') + * G G* (=P') + * d-> / \ / \ + * P* U* ==> P U + * / \ / \ + * (*)R S(*) (*)R S(*) + * + * (*) exactly one of R or S is red (since we have a red-violation + * at P) + * + * Note: this transformation preserves #of black nodes along path + * from root to each of {T, R, S}, so it preserves the "equal + * black-node path" property + */ + G->assign_color(C_Red); + P->assign_color(C_Black); + U->assign_color(C_Black); + + log && log("fixed red violation at P, retry 1 level higher"); + + /* still need to check for red-violation at G's parent */ + P = G; + G = G->parent(); + d = RbNode::child_direction(G, P); + + continue; + } + + assert(RbNode::is_black(U)); + + if (RbNode::is_red(S)) { + log && log("rotate-", (d == D_Left) ? "left" : "right", + " at P", xtag("P", P), xtag("P.key", P->key()), + xtag("S", S), xtag("S.key", S->key())); + + /* preparatory step: rotate P in d direction if "inner child" + * (S) is red inner-child = right-child of left-parent or vice + * versa + * + * G G + * / \ / \ + * P* U ==> (P'=) S* U + * / \ / \ + * R S* (R'=) P* + * / \ + * R + */ + RbTreeUtil::rotate(d, P, reduce_fn, pp_root); + + if (c_excessive_verify_enabled) + RbTreeUtil::verify_subtree_ok(reduce_fn, S, nullptr /*&black_height*/); + + /* (relabel S->P etc. for merged control flow below) */ + R = P; + P = S; + } + + /* + * G P + * / \ / \ + * P* U ==> R* G* + * / \ / \ + * R* S S U + * + * ok since every path that went through previously-black G + * now goes through newly-black P + */ + P->assign_color(C_Black); + G->assign_color(C_Red); + + log && log("rotate-", + (other_d == D_Left) ? "left" : "right", " at G", + xtag("G", G), xtag("G.key", G->key())); + + RbTreeUtil::rotate(other_d, G, reduce_fn, pp_root); + + if (c_excessive_verify_enabled) { + RbNode *GG = G ? G->parent() : G; + if (!GG) + GG = P; + + if (log.enabled()) { + log("verify subtree at GG", xtag("GG", GG), + xtag("GG.key", GG->key())); + + RbTreeUtil::verify_subtree_ok(reduce_fn, GG, nullptr /*&black_height*/); + RbTreeUtil::display_aux(D_Invalid, GG, 0 /*depth*/, &log); + + log("fixup complete"); + } + } + + return; + } /*walk toward root until red violation fixed*/ + } /*fixup_red_shape*/ + + /* insert key-value pair (key, value) into *pp_root. + * on exit *pp_root contains new tree with (key, value) inserted. + * returns true if node was inserted, false if instead an existing node + * with the same key was replaced. + * + * Require: + * - pp_root is non-nil (*pp_root may be nullptr -> empty tree) + * - *pp_root is in RB-shape + * + * allow_replace_flag. if true, v will replace an existing value + * associated with key k. + * if false, preserve existing value. + * when k already exists in *pp_root. + * + * return pair with: + * - f=true for new node (k did not exist in tree before this call) + * - f=false for existing node (k already in tree before this call) + * - n=node containing key k + */ + static std::pair + insert_aux(value_type const & kv_pair, + bool allow_replace_flag, + Reduce const & reduce_fn, + RbNode ** pp_root) + { + using xo::xtag; + + //XO_SCOPE2(log, true /*debug_flag*/); + + RbNode * N = *pp_root; + + Direction d = D_Invalid; + + while (N) { + if (kv_pair.first == N->key()) { + if(allow_replace_flag) { + /* match on this key already present in tree + * -> just update assoc'd value + */ + N->contents_.second = kv_pair.second; + } + + /* after modifying a node n, must recalculate reductions + * along path [root .. n] + */ + RbTreeUtil::fixup_ancestor_size(reduce_fn, N); + + //log && log(xtag("path", (char const *)"A")); + + /* since we didn't change the set of nodes, + * tree is still in RB-shape, don't need to call fixup_red_shape() + */ + return std::make_pair(false, N); + } + + d = ((kv_pair.first < N->key()) ? D_Left : D_Right); + + /* insert into left subtree somewhere */ + RbNode *C = N->child(d); + + if (!C) + break; + + N = C; + } + + /* invariant: N->child(d) is nil */ + + if (N) { + RbNode * new_node = RbNode::make_leaf(kv_pair, + reduce_fn.leaf(kv_pair.second)); + + N->assign_child_reparent(d, new_node); + + assert(is_red(N->child(d))); + + /* recalculate Node sizes on path [root .. N] */ + RbTreeUtil::fixup_ancestor_size(reduce_fn, N); + /* after adding a node, must rebalance to restore RB-shape */ + RbTreeUtil::fixup_red_shape(d, N, reduce_fn, pp_root); + + //log && log(xtag("path", (char const *)"B")); + + /* note: new_node=N.child(d) is true before call to fixup_red_shape(), + * but not necessarily after + */ + return std::make_pair(true, new_node); + } else { + *pp_root = RbNode::make_leaf(kv_pair, + reduce_fn.leaf(kv_pair.second)); + + /* tree with a single node might as well be black */ + (*pp_root)->assign_color(C_Black); + + //(*pp_root)->local_recalc_size(reduce_fn); + + /* Node.size will be correct for tree, since + * new node is only node in the tree + */ + + //log && log(xtag("path", (char const *)"C")); + + return std::make_pair(true, *pp_root); + } + + } /*insert_aux*/ + + /* remove a black node N with no children. + * this will reduce black-height along path to N + * by 1, so will need to rebalance tree + * + * pp_root. pointer to location of tree root; + * may update with new root + * + * Require: + * - N != nullptr + * - N has no child nodes + * - N->parent() != nullptr + */ + static void remove_black_leaf(RbNode *N, + Reduce const & reduce_fn, + RbNode **pp_root) + { + using xo::scope; + using xo::xtag; + using xo::print::ccs; + + //constexpr char const *c_self = "RbTreeUtil::remove_black_leaf"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + assert(pp_root); + + RbNode *P = N->parent(); + + if (!P) { + /* N was the root node, tree now empty */ + *pp_root = nullptr; + delete N; + return; + } + + /* d: direction in P to immediate child N; + * also sets N.parent to nil + */ + Direction d = P->replace_child_reparent(N, nullptr); + + delete N; + + /* need to delay this assignment until + * we've determined d + */ + N = nullptr; + + /* fixup sizes on path root..P + * subsequent rebalancing rotations will preserve correct .size values + */ + RbTreeUtil::fixup_ancestor_size(reduce_fn, P); + + /* other_d, S, C, D will be assigned by loop below + * + * diagram shown with d=D_Left; mirror left-to-right for d=D_Right + * + * P + * d-> / \ <-other_d + * N S + * / \ + * C D + */ + Direction other_d; + RbNode *S = nullptr; + RbNode *C = nullptr; + RbNode *D = nullptr; + + /* table of outcomes as a function of node color + * + * .=black + * *=red + * x=don't care + * + * #=#of combinations (/16) for P,S,C,D color explained by this row + * + * P S C D case # + * ----------------------- + * . . . . Case(1) 1 + * x * x x Case(3) 8 P,C,D black is forced by RB rules + * * . . . Case(4) 1 + * x . * . Case(5) 2 + * x . x * Case(6) 4 + * -- + * 16 + * + */ + + while (true) { + assert(is_black(N)); /* reminder: nil is black too */ + + /* Invariant: + * - either: + * - N is nil (first iteration only), and + * P->child(d) = nil, or: + * - P is nil and non-nil N is tree root, or: + * - N is an immediate child of P, + * and P->child(d) = N + * - N is black + * - all paths that don't go thru N have prevailing black-height h. + * - paths through N have black-height h-1 + */ + + if (!P) { + /* N is the root node, in which case all paths go through N, + * so black-height is h-1 + */ + *pp_root = N; + return; + } + + other_d = other(d); + S = P->child(other_d); + + /* S can't be nil: since N is non-nil and black, + * it must have a non-nil sibling + */ + assert(S); + + C = S->child(d); + D = S->child(other_d); + + if (log.enabled()) { + log("rebalance at parent P of curtailed subtree N", + xtag("P", P), + xtag("P.col", ccs(P->color() == C_Black ? "B" : "r")), + xtag("P.key", P->key())); + log("with sibling S, nephews C,D", xtag("S", S), + xtag("S.col", ccs(S->color() == C_Black ? "B" : "r")), + xtag("C", C), xtag("D", D)); + } + + if (is_black(P) && is_black(S) && is_black(C) && is_black(D)) { + /* Case(1) */ + + log && log("P,S,C,D all black: mark S red + go up 1 level"); + + /* diagram with d=D_Left: flip left-to-right for d=D_Right + * =black + * *=red + * _=red or black + * + * P + * / \ + * N S + * / \ + * C D + * + * relative to prevailing black-height h: + * - N at h-1 + * - C at h + * - D at h + */ + + S->assign_color(C_Red); + + /* now have: + * + * G (=P') + * | + * P (=N') + * / \ + * N S* + * / \ + * C D + * + * relative to prevailing black-height h: + * - N at h-1 + * - C at h-1 + * - D at h-1 + * + * relabel to one level higher in tree + */ + N = P; + P = P->parent(); + d = RbNode::child_direction(P, N); + + continue; + } else { + break; + } + } /*loop looking for a red node*/ + + if (is_red(S)) { + /* Case(3) */ + + if (log.enabled()) { + log("case 3: S red, P,C,D black -> rotate at P to promote S"); + log("case 3: + make P red instead of S"); + log("case 3: with", + xtag("P", P), + xtag("P.col", ccs(P->color() == C_Black ? "B" : "r")), + xtag("P.key", P->key()), xtag("S", S), + xtag("S.col", ccs(S->color() == C_Black ? "B" : "r")), + xtag("S.key", S->key())); + } + + /* since S is red, {P,C,D} are all black + * + * diagram with d=D_Left: flip left-to-right for d=D_Right + * =black + * *=red + * _=red or black + * + * P + * / \ + * N S* + * / \ + * C D + * + * relative to prevailing black-height h: + * - N at h-1 + * - C at h + * - D at h + */ + + assert(is_black(C)); + assert(is_black(D)); + assert(is_black(P)); + assert(is_black(N)); + + RbTreeUtil::rotate(d, P, reduce_fn, pp_root); + + /* after rotation d at P: + * + * S* + * / \ + * P D + * / \ + * N C + * + * relative to prevailing black-height h: + * - N at h-1 (now goes thru red S) + * - C at H (still goes through black P, red S) + * - D at h-1 (no longer goes thru black P) + */ + + P->assign_color(C_Red); + S->assign_color(C_Black); + + /* after reversing colors of {P,S}: + * + * S + * / \ + * P* D + * / \ + * N C (=S') + * + * relative to prevailing black-height h: + * - N at h-1 (now thru black S, red P instead of red S, black P) + * - C at h (now thru black S, red P instead of red S, black P) + * - D at h (now through black S instead of red S, black P) + */ + + /* now relabel for subsequent cases */ + S = C; + C = S ? S->child(d) : nullptr; + D = S ? S->child(other_d) : nullptr; + } + + assert(is_black(S)); + + if (is_red(P) && is_black(C) && is_black(D)) { + /* Case(4) */ + + if (log.enabled()) { + log("case 4: P red, N,S,C,D black -> recolor and finish"); + log("case 4: with", + xtag("P", P), + xtag("P.col", ccs(P->color() == C_Black ? "B" : "r")), + xtag("P.key", P->key()), xtag("S", S), + xtag("S.col", ccs(S->color() == C_Black ? "B" : "r")), + xtag("S.key", S->key())); + } + + assert(is_black(N)); + + /* diagram with d=D_Left: flip left-to-right for d=D_Right* + * =black + * *=red + * _=red or black + * + * P* + * / \ + * N S + * / \ + * C D + * + * relative to prevailing black-height h: + * - N at h-1 + * - C at h + * - D at h + */ + + P->assign_color(C_Black); + S->assign_color(C_Red); + + /* after making P black, and S red (swapping colors of P,S): + * + * P + * / \ + * N S* + * / \ + * C D + * + * relative to prevailing black-height h: + * - N at h + * - C at h + * - D at h + * + * and RB-shape is restored + */ + return; + } + + assert(is_black(S) && (is_black(P) || is_red(C) || is_red(D))); + + if (is_red(C) && is_black(D)) { + log && log("case 5: C red, S,D black -> rotate at S"); + + /* diagram with d=D_Left; flip left-to-right for d=D_Right + * + * =black + * *=red + * _=red or black + * + * P_ + * / \ + * N S + * / \ + * C* D + * + * relative to prevailing black-height h: + * - N at h-1 + * - C at h + * - D at h + */ + + RbTreeUtil::rotate(other_d, S, reduce_fn, pp_root); + + assert(P->child(other_d) == C); + + /* after other(d) rotation at S: + * + * P_ + * / \ + * N C* + * \ + * S + * \ + * D + * + * relative to prevailing black-height h: + * - N at h-1 + * - C at h-1 (no longer goes thru black S) + * - S at h (now goes thru red C) + * - D at h (now goes thru red C) + */ + + C->assign_color(C_Black); + S->assign_color(C_Red); + + /* after exchanging colors of C,S: + * + * P_ + * / \ + * N C (=S') + * \ + * S* (=D') + * \ + * D + * + * relative to prevailing black-height h: + * - N at h-1 + * - C at h (no longer goes thru black S, but now C black) + * - S at h (no longer red, but now goes thru black C) + * - D at h (now goes thru black C, red S instead of black S) + */ + + /* now relabel to match next and final case */ + D = S; + S = C; + C = nullptr; /* won't be using C past this point */ + + assert(D); + assert(D->is_red()); + + /* fall through to next case */ + } + + if (is_red(D)) { + log && log("case 6: S black, D red -> rotate at P and finish"); + + /* diagram with d=D_Left; flip left-to-right for d=D_Right + * + * Sibling is black, and distant child is red + * + * if N=P->left_child(): + * + * *=red + * _=red or black + * + * P_ + * / \ + * N S + * / \ + * C_ D* + * + * relative to prevailing black-height h: + * - N at h-1 + * - S (+also C,D) at h + */ + + RbTreeUtil::rotate(d, P, reduce_fn, pp_root); + + /* after rotate at P toward d: * + * + * S + * / \ + * P_ D* + * / \ + * N C_ + * + * Now, relative to prevailing black-height h: + * - N at h+1 (paths to N now visit black S) + * - C at h (paths to C still visit P,S) + * - D at: h if P red, + * h-1 if P black + * (paths to D now skip P) + */ + + S->assign_color(P->color()); + P->assign_color(C_Black); + D->assign_color(C_Black); + + /* after recolor: S to old P color, P to black, D to black. + * + * S_ + * / \ + * P D + * / \ + * N C_ + * + * Now, relative to prevailing black-height h: + * - N at h+1 (swapped P, S colors) + * - C at h (paths to C still visit P,S, swapped P,S colors) + * - D at: h if S red (was P red, S black, D red; now S red, D + * black) h if S black (was P black, S black, D red; now S + * black, D black) + * + * RB-shape has been restored + */ + return; + } + } /*remove_black_leaf*/ + + /* remove node with key k from tree rooted at *pp_root. + * on exit *pp_root contains new tree root. + * + * Require: + * - pp_root is non-null. (*pp_root can be null -> tree is empty) + * - *pp_root is in RB-shape + * + * return true if a node was removed; false otherwise. + */ + static bool erase_aux(Key const &k, + Reduce const & reduce_fn, + RbNode **pp_root) { + using xo::scope; + using xo::xtag; + + //constexpr char const *c_self = "RbTreeUtil::erase_aux"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + RbNode *N = *pp_root; + + log && log("enter", xtag("N", N)); + + /* + * here the triangle ascii art indicates a tree structure, + * of arbitrary size + * + * o <- this + * / \ + * o-N-o + * / \ + * X + * / \ + * o---R + */ + + N = RbTreeUtil::find_glb(N, k, true /*is_closed*/); + + if (!N || (N->key() != k)) { + /* no node with .key = k present, so cannot remove it */ + return false; + } + + if (c_logging_enabled) + log && log("got lower bound", xtag("N", N), + xtag("N.key", N->key())); + + /* first step is to simplify problem so that we're removing + * a node with 0 or 1 children. + */ + + RbNode *X = N->left_child(); + + if (X == nullptr) { + /* N has 0 or 1 children */ + ; + } else { + /* R will be 'replacement node' for N */ + RbNode *R = RbTreeUtil::find_rightmost(X); + + /* R->right_child() is nil by definition + * + * copy R's (key + value) into N; + * N now serves as container for information previously + * represented by R. + */ + + N->contents_ = R->contents_; + /* (preserving N->parent_, N->child_v_[]) */ + + /* now relabel N as new R (R'), + * and relabel R as new N (N'). + * Then go to work on reduced problem of deleting N'. + * Problem is redueced since now N' has 0 or 1 child. + * + * (Doesn't matter that N' contains key,values of R, + * since we're going to delete it anyway) + */ + N = R; + /* (preserving R->parent_, R->child_v_[]) */ + + /* o + * / \ + * o-R'o + * / + * X + * / \ + * o---N' + */ + } + + RbNode *P = N->parent(); + + /* N has 0 or 1 children + * + * Implications: + * 1. if N is red, it cannot have red children (by RB rules), + * and it cannot have just 1 black child. + * Therefore red N must have 0 children + * -> can delete N without disturbing RB properties + * 2. if N is black: + * 2.1 if N has 1 child S, then S must be red + * (if S were black, that would require N to have a 2nd child + * to preserve equal black-height for all paths) + * -> replace N with S, repainting S black, in place of + * to-be-reclaimed N + * 1.2 if N is black with 0 children, need to rebalance + */ + + if (N->is_red()) { + if (N->is_leaf()) { + /* replace pointer to N with nil in N's parent. */ + + if (P) { + P->replace_child_reparent(N, nullptr); + RbTreeUtil::fixup_ancestor_size(reduce_fn, P); + } else { + /* N was sole root node; tree will be empty after removing it */ + *pp_root = nullptr; + } + + if (c_logging_enabled) + log && log("delete node", xtag("addr", N)); + delete N; + } else { + assert(false); + + /* control can't come here for RB-tree, + * because a red node can't have red children, or just one black + * child. + */ + } + } else /*N->is_black()*/ { + RbNode *R = N->left_child(); + + if (!R) + R = N->right_child(); + + if (R) { + /* if a black node has one child, that child cannot be black */ + assert(R->is_red()); + + /* replace N with R in N's parent, + * + make R black to preserve black-height + */ + R->assign_color(C_Black); + + if (P) { + P->replace_child_reparent(N, R); + RbTreeUtil::fixup_ancestor_size(reduce_fn, P); + } else { + /* N was root node */ + RbNode::replace_root_reparent(R, pp_root); + } + + if (c_logging_enabled) + log && log("delete node", xtag("addr", N)); + delete N; + } else { + /* N is black with no children, + * may need rebalance here + */ + + if (P) { + RbTreeUtil::remove_black_leaf(N, reduce_fn, pp_root); + } else { + /* N was root node */ + *pp_root = nullptr; + + log && log("delete node", xtag("addr", N)); + delete N; + } + } + } + + return true; + } /*erase_aux*/ + + /* verify that subtree at N is in RB-shape. + * will cover subset of RedBlackTree class invariants: + * + * RB2. if N = P->child(d), then N->parent()=P + * RB3. all paths to leaves have the same black height + * RB4. no red node has a red parent + * RB5. inorder traversal visits keys in monotonically increasing order + * RB6. Node::size reports the size of the subtree reachable from that node + * via child pointers + * RB7. Node::reduced reports the value of + * f(f(L, Node::value), R) + * where: L is reduced-value for left child, + * R is reduced-value for right child + * + * returns the #of nodes in subtree rooted at N. + */ + static size_t verify_subtree_ok(Reduce const & reduce_fn, + RbNode const * N, + int32_t * p_black_height) + { + using xo::scope; + using xo::xtag; + using xo::print::ccs; + + constexpr char const *c_self = "RbTreeUtil::verify_subtree_ok"; + + // scope lscope(c_self); + + /* counts #of nodes in subtree rooted at N */ + size_t i_node = 0; + Key const *last_key = nullptr; + /* inorder node index when establishing black_height */ + size_t i_black_height = 0; + /* establish on first leaf node encountered */ + uint32_t black_height = 0; + + auto verify_fn = [c_self, + &reduce_fn, + &i_node, + &last_key, + &i_black_height, + &black_height] (RbNode const *x, + uint32_t bd) + { + /* RB2. if c=x->child(d), then c->parent()=x */ + + if (x->left_child()) { + XO_EXPECT(x == x->left_child()->parent(), + tostr(c_self, (": expect symmetric child/parent pointers"), + xtag("i", i_node), xtag("node[i]", x), + xtag("key[i]", x->key()), + xtag("child", x->left_child()), + xtag("child.key", x->left_child()->key()), + xtag("child.parent", x->left_child()->parent_))); + } + + if (x->right_child()) { + XO_EXPECT(x == x->right_child()->parent(), + tostr(c_self, ": expect symmetric child/parent pointers", + xtag("i", i_node), + xtag("node[i]", x), + xtag("key[i]", x->key()), + xtag("child", x->right_child()), + xtag("child.key", x->right_child()->key()), + xtag("child.parent", x->right_child()->parent_))); + } + + /* RB3. all nodes have the same black-height */ + + if (x->is_leaf()) { + if (black_height == 0) { + black_height = bd; + } else { + XO_EXPECT(black_height == bd, + tostr(c_self, + ": expect all RB-tree nodes to have the same " + "black-height", + xtag("i1", i_black_height), xtag("i2", i_node), + xtag("blackheight(i1)", black_height), + xtag("blackheight(i2)", bd))); + } + } + + /* RB4. a red node may not have a red parent + * (conversely, a red node may not have a red child) + */ + + RbNode *red_child = + ((x->left_child() && x->left_child()->is_red()) + ? x->left_child() + : ((x->right_child() && x->right_child()->is_red()) + ? x->right_child() + : nullptr)); + + XO_EXPECT( + x->is_red_violation() == false, + tostr(c_self, + ccs(": expect RB-shape tree to have no red violations but " + "red y is child of red x"), + xtag("i", i_node), xtag("x.addr", x), + xtag("x.col", ccs((x->color_ == C_Black) ? "B" : "r")), + xtag("x.key", x->key()), + xtag("y.addr", red_child), + xtag("y.col", ccs((red_child->color_ == C_Black) ? "B" : "r")), + xtag("y.key", red_child->key()))); + + /* RB5. inorder traversal visits nodes in strictly increasing key order */ + + if (last_key) { + XO_EXPECT((*last_key) < x->key(), + tostr(c_self, + ": expect inorder traversal to visit keys" + " in strictly increasing order", + xtag("i", i_node), xtag("key[i-1]", *last_key), + xtag("key[i]", x->key()))); + } + + last_key = &(x->key()); + + /* RB6. Node::size reports the size of the subtree reachable from that + * node by child pointers. + */ + XO_EXPECT(x->size() == (tree_size(x->left_child()) + + 1 + + tree_size(x->right_child())), + tostr(c_self, + ": expect Node::size to be 1 + sum of childrens' size", + xtag("i", i_node), + xtag("key[i]", x->key()), + xtag("left.size", tree_size(x->left_child())), + xtag("right.size", tree_size(x->right_child())))); + + /* RB7. Node::reduced reports the value of + * f(f(L, Node::value), R) + * where: L is reduced-value for left child, + * R is reduced-value for right child + */ + auto reduced_pair + = RbNode::reduced_pair(reduce_fn, x); + + XO_EXPECT(reduce_fn.is_equal + (x->reduced1(), reduced_pair.first), + tostr(c_self, + ": expect Node::reduced to be reduce_fn" + " applied to (.L, .value)", + xtag("node.reduced1", x->reduced1()), + xtag("reduced_pair.first", reduced_pair.first))); + + XO_EXPECT(reduce_fn.is_equal + (x->reduced2(), reduced_pair.second), + tostr(c_self, + ": expect Node::reduced to be reduce_fn" + " applied to (.L, .value, .R)", + xtag("node.reduced2", x->reduced2()), + xtag("reduce2_expr", reduced_pair.second))); + + ++i_node; + }; + + RbTreeUtil::inorder_node_visitor(N, 0 /*d*/, verify_fn); + + if (p_black_height) + *p_black_height = black_height; + + return i_node; + } /*verify_subtree_ok*/ + + /* display tree structure, 1 line per node. + * indent by node depth + d + */ + static void display_aux(Direction side, RbNode const *N, uint32_t d, + xo::scope *p_scope) { + using xo::pad; + using xo::xtag; + using xo::print::ccs; + + if (N) { + p_scope->log(pad(d), + xtag("addr", N), + xtag("par", N->parent()), + xtag("side", ccs((side == D_Left) ? "L" + : (side == D_Right) ? "R" + : "root")), + xtag("col", ccs(N->is_black() ? "B" : "r")), + xtag("key", N->key()), + xtag("value", N->value()), + xtag("wt", N->size()), + xtag("reduced1", N->reduced1()), + xtag("reduced2", N->reduced2())); + display_aux(D_Left, N->left_child(), d + 1, p_scope); + display_aux(D_Right, N->right_child(), d + 1, p_scope); + } + } /*display_aux*/ + + static void display(RbNode const *N, uint32_t d) { + using xo::scope; + + scope log(XO_DEBUG(true /*debug_flag*/)); + + display_aux(D_Invalid, N, d, &log); + } /*display*/ + }; /*RbTreeUtil*/ + + /* xo::tree::detail::RedBlackTreeLhsBase + * + * use for const version of RedBlackTree::operator[]. + * + * Require: RbNode is either + * RedBlackTree::RbNode + * or + * RedBlackTree::RbNode const + */ + template + class RedBlackTreeLhsBase { + public: + using mapped_type = typename RedBlackTree::mapped_type; + using RbUtil = typename RedBlackTree::RbUtil; + + public: + RedBlackTreeLhsBase() = default; + RedBlackTreeLhsBase(RedBlackTree * tree, RbNode * node) + : p_tree_(tree), node_(node) + {} + + operator mapped_type const & () const { + using xo::tostr; + + if (!this->node_) { + throw std::runtime_error + (tostr("rbtree: attempt to use empty lhs object as rvalue")); + } + + return this->node_->contents().second; + } /*operator value_type const &*/ + + protected: + RedBlackTree * p_tree_ = nullptr; + /* invariant: if non-nil, .node belongs to .*p_tree */ + RbNode * node_ = nullptr; + }; /*RedBlackTreeLhsBase*/ + + template + class RedBlackTreeConstLhs : public RedBlackTreeLhsBase + { + public: + RedBlackTreeConstLhs() = default; + RedBlackTreeConstLhs(RedBlackTree const * tree, + typename RedBlackTree::RbNode const * node) + : RedBlackTreeLhsBase(tree, node) {} + }; /*RedBlackTreeConstLhs*/ + + /* xo::tree::detail::RedBlackTreeLhs + * + * use for RedBlackTree::operator[]. + * can't return a regular lvalue, + * because assignment within a Node N invalidates partial sums along + * the path from tree root to N. + * + * instead interpolate instance of this class, that can intercept + * asasignments. + */ + template + class RedBlackTreeLhs : public RedBlackTreeLhsBase + { + public: + using value_type = typename RedBlackTree::value_type; + using key_type = typename RedBlackTree::key_type; + using mapped_type = typename RedBlackTree::mapped_type; + using RbUtil = typename RedBlackTree::RbUtil; + using RbNode = typename RedBlackTree::RbNode; + + public: + RedBlackTreeLhs() = default; + RedBlackTreeLhs(RedBlackTree * tree, typename RedBlackTree::RbNode * node, key_type key) + : RedBlackTreeLhsBase(tree, node), key_(key) {} + + RedBlackTreeLhs & operator=(mapped_type const & v) { + using xo::tostr; + + if(this->p_tree_) { + if(this->node_) { + this->node_->contents().second = v; + + /* after modifying a node n, + * must recalculate reductions along path [root .. n] + */ + RbUtil::fixup_ancestor_size(this->p_tree_->reduce_fn(), + this->node_); + } else { + /* insert (key, v) pair into this tree */ + this->p_tree_->insert(value_type(this->key_, v)); + } + } else { + assert(false); + + throw std::runtime_error + (tostr("rbtree: attempt to apply operator= thru empty lhs object")); + } + + return *this; + } /*operator=*/ + + RedBlackTreeLhs & operator+=(mapped_type const & v) { + using xo::tostr; + + if(this->p_tree_) { + if(this->node_) { + this->node_->contents().second += v; + + /* after modifying value at node n, + * must recalculate order statistics along path [root .. n] + */ + RbUtil::fixup_ancestor_size(this->p_tree_->reduce_fn(), + this->node_); + } else { + /* for form's sake, in case value_type is something unusual */ + mapped_type v2; + v2 += v; + + /* insert (key, v) pair into this tree */ + this->p_tree_->insert(value_type(this->key_, v2)); + } + } else { + assert(false); + + throw std::runtime_error + (tostr("rbtree: attempt to apply operator+= through empty lhs object")); + } + + return *this; + } /*operator+=*/ + + /* TODO: + * - operator-=() + * - operator*=() + * - operator/=() + */ + + private: + /* capture key k used in expression tree[k] + * Invariant: + * - if .node is non-null, then .node.key = key + */ + key_type key_; + }; /*RedBlackTreeLhs*/ + + /* tragically, we can't partially specialize an alias template. + * however we /can/ partially specialize a struct that nests a typealias. + */ + template + struct NodeTypeTraits { using NodeType = void; }; + + template + struct NodeTypeTraits { + using NativeNodeType = Node; + using NodeType = NativeNodeType; + using ContentsType = typename NodeType::ContentsType; + using NodePtrType = NodeType *; + }; + + template + struct NodeTypeTraits { + using NativeNodeType = Node; + using NodeType = NativeNodeType const; + using ContentsType = typename NodeType::ContentsType const; + using NodePtrType = NodeType const *; + }; + + /* xo::tree::detail::IteratorBase + * + * shared between const & and non-const red-black-tree iterators. + * + * editor bait: BaseIterator + */ + template + class IteratorBase { + public: + using RbUtil = RbTreeUtil; + using RbNode = Node; + using Traits = NodeTypeTraits; + using ReducedValue = typename Reduce::value_type; + using RbNativeNodeType = typename Traits::NativeNodeType; + using RbNodePtrType = typename Traits::NodePtrType; + using RbContentsType = typename Traits::ContentsType; + + protected: + IteratorBase() = default; + IteratorBase(IteratorDirection dirn, IteratorLocation loc, RbNodePtrType node) + : dirn_{dirn}, location_{loc}, node_{node} {} + IteratorBase(IteratorBase const & x) = default; + + static IteratorBase prebegin_aux(RbNodePtrType node) { + return IteratorBase(ID_Forward, IL_BeforeBegin, node); + } /*prebegin_aux*/ + + static IteratorBase begin_aux(RbNodePtrType node) { + return IteratorBase(ID_Forward, node ? IL_Regular : IL_AfterEnd, node); + } /*begin_aux*/ + + static IteratorBase end_aux(RbNodePtrType node) { + return IteratorBase(ID_Forward, IL_AfterEnd, node); + } /*end_aux*/ + + static IteratorBase rprebegin_aux(RbNodePtrType node) { + return IteratorBase(ID_Reverse, IL_AfterEnd, node); + } /*rprebegin_aux*/ + + static IteratorBase rbegin_aux(RbNodePtrType node) { + return IteratorBase(ID_Reverse, + (node ? IL_Regular : IL_BeforeBegin), + node); + } /*rbegin_aux*/ + + static IteratorBase rend_aux(RbNodePtrType node) { + return IteratorBase(ID_Reverse, + IL_BeforeBegin, + node); + } /*rend_aux*/ + + public: + IteratorLocation location() const { return location_; } + RbNodePtrType node() const { return node_; } + + ReducedValue const & reduced() const { return node_->reduced(); } + + RbContentsType & operator*() const { + this->check_regular(); + return this->node_->contents(); + } /*operator**/ + + RbContentsType * operator->() const { + return &(this->operator*()); + } + + /* true for "just before beginning" and "just after the end" states. + * false otherwise + */ + bool is_sentinel() const { return (this->location_ != IL_Regular); } + /* true unless iterator is in a sentinel state */ + bool is_dereferenceable() const { return !this->is_sentinel(); } + + /* deferenceable iterators are truth-y; + * sentinel iterators are false-y + */ + operator bool() const { return this->is_dereferenceable(); } + + bool operator==(IteratorBase const & x) const { + return (this->location_ == x.location_) && (this->node_ == x.node_); + } /*operator==*/ + + bool operator!=(IteratorBase const & x) const { + return (this->location_ != x.location_) || (this->node_ != x.node_); + } /*operator!=*/ + + void print(std::ostream & os) const { + using xo::xtag; + + os << ""; + } /*print*/ + + /* pre-increment */ + IteratorBase & operator++() { + return ((this->dirn_ == ID_Forward) + ? this->next_step() + : this->prev_step()); + } /*operator++*/ + + /* pre-decrement */ + IteratorBase & operator--() { + return ((this->dirn_ == ID_Forward) + ? this->prev_step() + : this->next_step()); + } /*operator--*/ + + protected: + void check_regular() const { + using xo::tostr; + + if(this->location_ != IL_Regular) + throw std::runtime_error(tostr("rbtree iterator: cannot deref iterator" + " in non-regular state")); + } /*check_regular*/ + + private: + IteratorBase & next_step() { + switch(this->location_) { + case IL_BeforeBegin: + /* .node is first node in tree */ + this->location_ = IL_Regular; + break; + case IL_Regular: + { + RbNodePtrType next_node + = RbUtil::next_inorder_node(const_cast(this->node_)); + + if(next_node) { + this->node_ = next_node; + } else { + this->location_ = IL_AfterEnd; + } + } + break; + case IL_AfterEnd: + break; + } /*operator++*/ + + return *this; + } /*next_step*/ + + IteratorBase & prev_step() { + switch(this->location_) { + case IL_BeforeBegin: + break; + case IL_Regular: + { + RbNode * prev_node = RbUtil::prev_inorder_node(const_cast(this->node_)); + + if(prev_node) { + this->node_ = prev_node; + } else { + this->location_ = IL_BeforeBegin; + } + } + break; + case IL_AfterEnd: + /* .node is already last node in tree */ + this->location_ = IL_Regular; + break; + } + + return *this; + } /*prev_step*/ + + protected: + /* ID_Forward, ID_Reverse */ + IteratorDirection dirn_ = ID_Forward; + /* IL_BeforeBegin, IL_Regular, IL_AfterEnd */ + IteratorLocation location_ = IL_AfterEnd; + /* location = IL_BeforeBegin: .node is leftmost node in tree + * location = IL_Regular: .node is some node in tree, + * iterator refers to that node. + * location = IL_AfterEnd: .node is rightmost node in tree + */ + RbNodePtrType node_ = nullptr; + }; /*IteratorBase*/ + + /* xo::tree::detail::Iterator + * + * inorder iterator over nodes in a red-black tree. + * invalidated on insert or remove operations on the parent tree. + * + * satisfies the std::bidirectional_iterator concept + */ + template + class Iterator : public IteratorBase { + public: + using iterator_concept = std::bidirectional_iterator_tag; + + using RbIteratorBase = IteratorBase; + using RbNode = typename RbIteratorBase::RbNode; + using RbUtil = typename RbIteratorBase::RbUtil; + using ReducedValue = typename Reduce::value_type; + + public: + Iterator() = default; + Iterator(IteratorDirection dirn, IteratorLocation loc, RbNode * n) + : RbIteratorBase(dirn, loc, n) {} + Iterator(Iterator const & x) = default; + Iterator(RbIteratorBase const & x) : RbIteratorBase(x) {} + Iterator(RbIteratorBase && x) : RbIteratorBase(std::move(x)) {} + + static Iterator begin_aux(RbNode const * n) { return RbIteratorBase::begin_aux(n); } + static Iterator end_aux(RbNode const * n) { return RbIteratorBase::end_aux(n); } + + static Iterator rbegin_aux(RbNode const * n) { return RbIteratorBase::rbegin_aux(n); } + static Iterator rend_aux(RbNode const * n) { return RbIteratorBase::rend_aux(n); } + + /* pre-increment */ + Iterator & operator++() { + RbIteratorBase::operator++(); + return *this; + } /*operator++*/ + + /* post-increment */ + Iterator operator++(int) { + Iterator retval = *this; + + ++(*this); + + return retval; + } /*operator++(int)*/ + + /* pre-decrement */ + Iterator & operator--() { + RbIteratorBase::operator--(); + return *this; + } /*operator--*/ + + /* post-decrement */ + Iterator operator--(int) { + Iterator retval = *this; + + --(*this); + + return retval; + } /*operator--(int)*/ + }; /*Iterator*/ + + /* xo::tree::detail::ConstIterator + * + * inorder iterator over nodes in a red-black tree. + * invalidated on insert or remove operations on the parent tree. + * + * satisfies the std::bidirectional_iterator concept + */ + template + class ConstIterator : public IteratorBase { + public: + using iterator_concept = std::bidirectional_iterator_tag; + + using RbIteratorBase = IteratorBase; + using RbNode = typename RbIteratorBase::RbNode; + using RbUtil = typename RbIteratorBase::RbUtil; + using ReducedValue = typename Reduce::value_type; + + public: + ConstIterator() = default; + ConstIterator(IteratorDirection dirn, IteratorLocation loc, RbNode const * node) + : RbIteratorBase(dirn, loc, node) {} + ConstIterator(ConstIterator const & x) = default; + ConstIterator(RbIteratorBase const & x) : RbIteratorBase(x) {} + ConstIterator(RbIteratorBase && x) : RbIteratorBase(std::move(x)) {} + + static ConstIterator prebegin_aux(RbNode const * n) { return RbIteratorBase::prebegin_aux(n); } + static ConstIterator begin_aux(RbNode const * n) { return RbIteratorBase::begin_aux(n); } + static ConstIterator end_aux(RbNode const * n) { return RbIteratorBase::end_aux(n); } + + static ConstIterator rprebegin_aux(RbNode const * n) { return RbIteratorBase::rprebegin_aux(n); } + static ConstIterator rbegin_aux(RbNode const * n) { return RbIteratorBase::rbegin_aux(n); } + static ConstIterator rend_aux(RbNode const * n) { return RbIteratorBase::rend_aux(n); } + + /* pre-increment */ + ConstIterator & operator++() { + RbIteratorBase::operator++(); + return *this; + } /*operator++*/ + + /* post-increment */ + ConstIterator operator++(int) { + ConstIterator retval = *this; + + ++(*this); + + return retval; + } /*operator++(int)*/ + + /* pre-decrement */ + ConstIterator & operator--() { + RbIteratorBase::operator--(); + return *this; + } /*operator--*/ + + /* post-decrement */ + ConstIterator operator--(int) { + ConstIterator retval = *this; + + --(*this); + + return retval; + } /*operator--(int)*/ + }; /*ConstIterator*/ + } /*namespace detail*/ + + struct null_reduce_value {}; + + /* for null reduce, just have it return empty struct; + * otherwise breaks verification (e.g. verify_subtree_ok() below) + */ + template + struct NullReduce { + static constexpr bool is_null_reduce() { return true; } + static constexpr bool is_monotonic() { return false; } + + /* data type for reduced values */ + using value_type = null_reduce_value; + + value_type nil() const { return value_type(); } + value_type leaf(NodeValue const & /*x*/) const { + return nil(); + } + value_type operator()(value_type /*x*/, + NodeValue const & /*value*/) const { return nil(); } + value_type combine(value_type /*x*/, + value_type /*y*/) const { return nil(); } + bool is_equal(value_type /*x*/, value_type /*y*/) const { return true; } + }; /*NullReduce*/ + + inline std::ostream & operator<<(std::ostream & os, + null_reduce_value /*x*/) + { + os << "{}"; + return os; + } /*operator<<*/ + + /* just counts #of distinct values; + * redundant, same as detail::Node<>::size_. + * providing for completeness' sake + */ + template + class OrdinalReduce { + public: + using value_type = std::size_t; + + public: + static constexpr bool is_monotonic() { return true; } + + value_type nil() const { return 0; } + + value_type leaf(Value const & /*x*/) const { + return 1; + } /*leaf*/ + + value_type operator()(value_type acc, + Value const & /*x*/) const { + /* counts #of values */ + return acc + 1; + } + + value_type combine(value_type x, value_type y) const { return x + y; } + bool is_equal(value_type x, value_type y) const { return x == y; } + }; /*OrdinalReduce*/ + + /* reduction for inverting the integral of a non-negative discrete function + * computes sum of values for each subtree + */ + template + struct SumReduce { + using value_type = Value; + + static constexpr bool is_monotonic() { return true; } + + value_type nil() const { return -std::numeric_limits::infinity(); } + value_type leaf(Value const & x) const { + return x; + } /*leaf*/ + + value_type operator()(value_type reduced, + Value const & x) const { + /* sums tree values */ + if(std::isfinite(reduced)) { + return reduced + x; + } else { + /* omit -oo reduced value from .nil() */ + return x; + } + } /*operator()*/ + + value_type combine(value_type const & x, + value_type const & y) const { + /* omit -oo reduced value from .nil() */ + if(!std::isfinite(x)) + return y; + if(!std::isfinite(y)) + return x; + + return x + y; + } /*combine*/ + + bool is_equal(value_type const & x, value_type const & y) const { return x == y; } + }; /*SumReduce*/ + + /* red-black tree with order statistics + */ + template + class RedBlackTree { + static_assert(ReduceConcept); + //static_assert(requires(Reduce r) { r.nil(); }, "missing .nil() method"); + + public: + using key_type = Key; + using mapped_type = Value; + using value_type = std::pair; + using ReducedValue = typename Reduce::value_type; + using RbTreeLhs = detail::RedBlackTreeLhs>; + using RbTreeConstLhs = detail::RedBlackTreeConstLhs>; + using RbUtil = detail::RbTreeUtil; + using RbNode = detail::Node; + using Direction = detail::Direction; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using iterator = detail::Iterator; + using const_iterator = detail::ConstIterator; + + public: + RedBlackTree() = default; + + bool empty() const { return size_ == 0; } + size_type size() const { return size_; } + size_type max_size() const { return std::numeric_limits::max(); } + Reduce const & reduce_fn() const { return reduce_fn_; } + + /* forward const iterators (canonical names) */ + + /* iterator "one before beginning" */ + const_iterator cprebegin() const { + return const_iterator::prebegin_aux(RbUtil::find_leftmost(this->root_)); + } /*cprebegin*/ + + const_iterator cbegin() const { + return const_iterator::begin_aux(RbUtil::find_leftmost(this->root_)); + } /*begin*/ + + const_iterator cend() const { + return const_iterator::end_aux(RbUtil::find_rightmost(this->root_)); + } /*end*/ + + /* forward const iterators (overloaded names) */ + + const_iterator prebegin() const { return this->cprebegin(); } + const_iterator begin() const { return this->cbegin(); } + const_iterator end() const { return this->cend(); } + + /* forward non-const iterators */ + + iterator prebegin() { + return iterator::prebegin_aux(RbUtil::find_leftmost(this->root_)); + } /*prebegin*/ + + iterator begin() { + return iterator::begin_aux(RbUtil::find_leftmost(this->root_)); + } /*begin*/ + + iterator end() { + return iterator::end_aux(RbUtil::find_rightmost(this->root_)); + } /*end*/ + + /* reverse const iterators (canonical names) */ + + /* reverse-iterator, "one after end" */ + const_iterator crprebegin() const { + return const_iterator::rprebegin_aux(RbUtil::find_rightmost(this->root_)); + } /*crprebegin*/ + + const_iterator crbegin() const { + return const_iterator::rbegin_aux(RbUtil::find_rightmost(this->root_)); + } /*crbegin*/ + + const_iterator crend() const { + return const_iterator::rend_aux(RbUtil::find_leftmost(this->root_)); + } /*crend*/ + + /* reverse const iterators (overloaded names) */ + + const_iterator rprebegin() const { return this->crprebegin(); } + const_iterator rbegin() const { return this->crbegin(); } + const_iterator rend() const { return this->crend(); } + + /* reverse non-const iterators */ + + iterator rprebegin() { + return iterator::rprebegin_aux(RbUtil::find_rightmost(this->root_)); + } /*rprebegin*/ + + iterator rbegin() { + return iterator::rbegin_aux(RbUtil::find_rightmost(this->root_)); + } /*rbegin*/ + + iterator rend() { + return iterator::rend_aux(RbUtil::find_leftmost(this->root_)); + } /*rend*/ + + /* require: + * - .size() > 0 + */ + Key const & min_key() const { return this->cbegin().first; } + /* require: + * - .size() > 0 + */ + Key const & max_key() const { const_iterator ix = this->cend(); --ix; return ix->first; } + + /* visit tree contents in increasing key order + * + * Require: + * - Fn(std::pair const &) + */ + template + void visit_inorder(Fn && fn) { + auto visitor_fn = [&fn](RbNode const * x, uint32_t /*d*/) { fn(x->contents()); }; + + RbUtil::inorder_node_visitor(this->root_, + 0 /*depth -- will be ignored*/, + visitor_fn); + } /*visit_inorder*/ + + /* if i in [0 .. .size], return iterator referring to ith inorder node in tree + * otherwise return this->end() + */ + const_iterator find_ith(uint32_t i) const { + RbNode * node = RbUtil::find_ith(this->root_, i); + + if(node) { + return const_iterator(detail::ID_Forward, detail::IL_Regular, node); + } else { + return this->end(); + } + } /*find_ith*/ + + iterator find_ith(uint32_t i) { + RbNode * node = RbUtil::find_ith(this->root_, i); + + if(node) { + return iterator(detail::IL_Regular, node); + } else { + return this->end(); + } + } /*find_ith*/ + + /* find node with key equal to x in this tree. + * on success, return iterator ix with ix->first = x. + * on failure, return this->end() + */ + const_iterator find(Key const & x) const { + RbNode * node = RbUtil::find(this->root_, x); + + if(node) { + return const_iterator(detail::ID_Forward, detail::IL_Regular, node); + } else { + return this->end(); + } + } /*find*/ + + iterator find(Key const & x) { + RbNode * node = RbUtil::find(this->root_, x); + + if (node) { + return const_iterator(detail::ID_Forward, detail::IL_Regular, node); + } else { + return this->end(); + } + } /*find*/ + + /* find node in tree with largest key k such that: + * k <= x, if is_closed + * k < x, if !is_closed + * + * return iterator to that node. + * + * If no such node exists, return the same value as this->cprebegin(); + * + * This satisfies continuity property: + * if: ix = find_glb(k, is_closed), + * then: ix+1 = find_lub(k, !is_closed) + * + * even when ix.is_dereferenceable() is false + */ + const_iterator find_glb(Key const & k, bool is_closed) const { + RbNode * node = RbUtil::find_glb(this->root_, k, is_closed); + + if (node) { + return const_iterator(detail::ID_Forward, + detail::IL_Regular, + node); + } else { + return this->cprebegin(); + } + } /*find_glb*/ + + const_iterator find_lub(Key const & k, bool is_closed) const { + const_iterator ix = this->find_glb(k, !is_closed); + return ++ix; + } /*find_lub*/ + + /* RbTreeConstLhs provides rvalue-substitute for lookup-only in const RedBlackTree + * instances + */ + RbTreeConstLhs operator[](Key const & k) const + { + RbNode const * node = RbUtil::find(this->root_, k); + + return RbTreeConstLhs(this, node); + } /*operator[]*/ + + /* RbTreeLhs defers assignment, so that rbtree can update values of + * Node::reduce along path from root to Node n with n.key = k + * + * + * Note: + * 1. return value remains valid across subsequent inserts and assignments, + * so this is legal: + * RbTree rbtree = ...; + * auto v = rbtree[key1]; + * + * rbtree[key2] = ...; + * rbtree.insert(key3, value3); + * + * v = ...; + * + * 2. return value is not valid across removes, even of distinct keys, + * so this is ILLEGAL: + * RbTree rbtree = ...; + * auto v = rbtree[key1]; + * + * assert(key1 != key2); + * + * rbtree.remove(key2); + * + * v = ...; // undefined behavior, + * // v.node contents may have been copied and v.node deleted + */ + RbTreeLhs operator[](Key const & k) { + std::pair insert_result + = RbUtil::insert_aux(value_type(k, Value() /*used iff creating new node*/), + false /*allow_replace_flag*/, + this->reduce_fn_, + &(this->root_)); + + return RbTreeLhs(this, insert_result.second, k); + } /*operator[]*/ + + /* compute value of reduce applied to the set K of all keys k[j] in subtree + * N with: + * - k[j] <= lub_key if is_closed = true + * - k[j] < lub_key if is_closed = false + * return reduce_fn.nil() if K is empty + */ + ReducedValue reduce_lub(Key const &lub_key, bool is_closed) const { + return RbUtil::reduce_lub(lub_key, + this->reduce_fn_, + is_closed, + this->root_); + } /*reduce_lub*/ + + /* Provided Reduce computes sum, and we call this rbtree f + * with keys k[i] and values v[i]: + * + * returns iterator pointing to i'th key-value pair {k[i],v[i]} in this tree, + * with reduced value r(i) (i.e. RbNode::reduced1); + * where r(i) is the result of reducing all values v[j] with j<=i + * + * editor bait: invert_integral + */ + const_iterator cfind_sum_glb(ReducedValue const & y) const { + using xo::tostr; + using xo::xtag; + + //char const * c_self = "RedBlackTree::find_sum_glb"; + + RbNode * N = RbUtil::find_sum_glb(this->reduce_fn_, + this->root_, + y); + + if(!N) { + /* for no-lower-bound edge cases, return iterator ix + * pointing to 'before the beginning' of this tree. + * + * will have + * ix.is_deferenceable() == false + * (bool)ix == false + */ + return const_iterator(detail::ID_Forward, + detail::IL_BeforeBegin, + RbUtil::find_leftmost(this->root_)); + } + + return const_iterator(detail::ID_Forward, + detail::IL_Regular, + N); + } /*cfind_sum_glb*/ + + const_iterator find_sum_glb(ReducedValue const & y) const { + return this->cfind_sum_glb(y); + } /*find_sum_glb*/ + + /* non-const version of .cfind_sum_glb() */ + iterator find_sum_glb(ReducedValue const & y) { + const_iterator ix = this->cfind_sum_glb(y); + + return iterator(ix.location(), + const_cast(ix.node())); + } /*find_sum_glb*/ + + void clear() { + auto visitor_fn = [](RbNode const * x, uint32_t /*d*/) { + /* RbUtil.postorder_node_visitor() isn't expecting us to + * alter node, but will not examine it after it's deleted + */ + RbNode * xx = const_cast(x); + + delete xx; + }; + + RbUtil::postorder_node_visitor(this->root_, + 0 /*depth -- ignored by lambda*/, + visitor_fn); + + this->size_ = 0; + this->root_ = nullptr; + } /*clear*/ + + std::pair + insert(std::pair const & kv_pair) { + std::pair insert_result + = RbUtil::insert_aux(kv_pair, + true /*allow_replace_flag*/, + this->reduce_fn_, + &(this->root_)); + + if (insert_result.first) + ++(this->size_); + + return (std::pair + (iterator(detail::ID_Forward, + detail::IL_Regular, + insert_result.second), + insert_result.first)); + } /*insert*/ + + std::pair + insert(std::pair && kv_pair) { + using xo::scope; + using xo::xtag; + + constexpr bool c_logging_enabled = false; + scope log(XO_DEBUG(c_logging_enabled)); + + std::pair insert_result + = RbUtil::insert_aux(std::move(kv_pair), + true /*allow_replace_flag*/, + this->reduce_fn_, + &(this->root_)); + + if (insert_result.first) + ++(this->size_); + + return (std::pair + (iterator(detail::ID_Forward, + detail::IL_Regular, + insert_result.second), + insert_result.first)); + } /*insert*/ + + bool erase(Key const & k) { + bool retval = RbUtil::erase_aux(k, + this->reduce_fn_, + &(this->root_)); + + if (retval) + --(this->size_); + + return retval; + } /*erase*/ + + /* verify class invariants. + * unless implementation is broken, or client manages + * to violate api rules, this will always return true. + * + * RB0. if root node is nil then .size is 0 + * RB1. if root node is non-nil, then root->parent() is nil, + * and .size = root->size + * RB2. if N = P->child(d), then N->parent()=P + * RB3. all paths to leaves have the same black height + * RB4. no red node has a red parent + * RB5. inorder traversal visits keys in monotonically increasing order + * RB6. Node::size reports the size of the subtree reachable from that node + * via child pointers + * RB7. Node::reduced reports the value of + * f(f(L, Node::value), R) + * where: L is reduced-value for left child, + * R is reduced-value for right child + * RB8. RedBlackTree.size() equals the #of nodes in tree + */ + bool verify_ok(bool /*throw_flag_not_implemented*/ = true) const { + using xo::scope; + using xo::tostr; + using xo::xtag; + + constexpr const char *c_self = "RedBlackTree::verify_ok"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + /* RB0. */ + if (root_ == nullptr) { + XO_EXPECT(size_ == 0, tostr(c_self, ": expect .size=0 with null root", + xtag("size", size_))); + } + + /* RB1. */ + if (root_ != nullptr) { + XO_EXPECT(root_->parent_ == nullptr, + tostr(c_self, ": expect root->parent=nullptr", + xtag("parent", root_->parent_))); + XO_EXPECT(root_->size_ == this->size_, + tostr(c_self, ": expect self.size=root.size", + xtag("self.size", size_), + xtag("root.size", root_->size_))); + } + + /* height (counting only black nodes) of tree */ + int32_t black_height = 0; + + /* n_node: #of nodes in this->root_ */ + size_t n_node = RbUtil::verify_subtree_ok(this->reduce_fn_, + this->root_, + &black_height); + + /* RB8. RedBlackTree.size() equals #of nodes in tree */ + XO_EXPECT(n_node == this->size_, + tostr(c_self, ": expect self.size={#of nodes n in tree}", + xtag("self.size", size_), + xtag("n", n_node))); + + if (c_logging_enabled) + log && log(xtag("size", this->size_), + xtag("blackheight", black_height)); + + return true; + } /*verify_ok*/ + + void display() const { RbUtil::display(this->root_, 0); } /*display*/ + + private: + /* #of key/value pairs in this tree */ + size_t size_ = 0; + /* root of red/black tree */ + RbNode * root_ = nullptr; + /* .reduce_fn :: (Accumulator x Key) -> Accumulator */ + Reduce reduce_fn_; + }; /*RedBlackTree*/ + + template + inline std::ostream & + operator<<(std::ostream &os, + RedBlackTree const &tree) + { + tree.display(); + return os; + } /*operator<<*/ + + template + inline std::ostream & + operator<<(std::ostream & os, + detail::IteratorBase const & iter) + { + iter.print(os); + return os; + } /*operator<<*/ + + } /*namespace tree*/ +} /*namespace xo*/ + +/* end RedBlackTree.hpp */ diff --git a/include/xo/tree/bplustree/BplusTreeUtil.hpp b/include/xo/tree/bplustree/BplusTreeUtil.hpp new file mode 100644 index 00000000..f2da0263 --- /dev/null +++ b/include/xo/tree/bplustree/BplusTreeUtil.hpp @@ -0,0 +1,280 @@ +/* @file BplusTreeNode.hpp */ + +#pragma once + +#include "IteratorUtil.hpp" +#include "bplustree_tags.hpp" +#include "indentlog/scope.hpp" +#include "indentlog/print/tag.hpp" +#include // for std::unique_ptr +#include + +namespace xo { + namespace tree { + /* forward decl (see GenericNode.hpp) */ + template + class GenericNode; + /* forward decl (see InternalNode.hpp) */ + template + class InternalNode; + + namespace detail { + /* forward decl (see Iterator.hpp) */ + template + class ConstIterator; + } + + // ----- NodeType ----- + + enum class NodeType { internal, leaf }; + + inline std::string node_type2str(NodeType x) { + switch(x) { + case NodeType::internal: return "internal"; + case NodeType::leaf: return "leaf"; + } + + return "???"; + } /*node_type2str*/ + + inline std::ostream & operator<<(std::ostream & os, NodeType x) { + os << node_type2str(x); + return os; + } /*operator<<*/ + + /* see bplustree/LeafNode.hpp */ + template + struct LeafNode; + + /* see bplustree/InternalNode.hpp */ + template + struct InternalNode; + + // ----- NodeItem + NodeItemPlaceholder ----- + + template + struct NodeItem {}; + + /* struct with same size as NodeItem, but POD + with trivial ctor/dtor */ + template + struct NodeItemPlaceholder { + std::uint8_t mem_v_[sizeof(NodeItem)]; + }; /*NodeItemPlaceholder*/ + + // ----- FindResult ----- + + /* report a node, along with its location (0-based index) within parent. + * use nullptr for .node if item/node not found + * use 0 for .ix if node is root (i.e. has no parent) + * + * expect ConcreteNodeType = LeafNode<..> | InternalNode<..> + */ + template + struct FindNodeResult { + public: + FindNodeResult() = default; + FindNodeResult(FindNodeResult const & x) = default; + FindNodeResult(std::size_t ix, ConcreteNodeType * node) : ix_{ix}, node_{node} {} + + std::size_t ix() const { return ix_; } + ConcreteNodeType * node() const { return node_; } + + private: + /* 0-based index within parent */ + std::size_t ix_ = 0; + /* a B+ tree node */ + ConcreteNodeType * node_ = nullptr; + }; /*FindNodeResult*/ + + template + struct BplusTreeUtil { + public: + using GenericNodeType = GenericNode; + using InternalNodeType = InternalNode; + using LeafNodeType = LeafNode; + using const_iterator = detail::ConstIterator; + + static std::size_t get_node_size(GenericNodeType const * node) { + return node->size(); + } + + /* only implemented for OrdinalTag = ordinal_enabled */ + static void print_node_size(std::ostream & os, GenericNodeType const * node) { + using xo::xtag; + + os << (node ? node->size() : 0UL); + } + + static const_iterator find_ith(GenericNodeType * generic_node, + std::size_t i_tree, + const_iterator cend) { + using xo::xtag; + + if (!generic_node) + return cend; + + std::size_t iter = 0; + + /* 100-level B+ tree won't fit in memory -- would have at least 2^100 nodes! */ + while (iter < 100) { + switch (generic_node->node_type()) { + case NodeType::leaf: + return const_iterator(detail::ID_Forward /*dirn*/, + detail::IL_Regular /*loc*/, + reinterpret_cast(generic_node), + i_tree /*item_ix*/); + case NodeType::internal: + { + /* scan for ith member (counting from 0) */ + + InternalNodeType const * internal_node + = reinterpret_cast(generic_node); + + std::size_t sum_z = 0; + std::size_t z = 0; + + std::size_t i = 0; + std::size_t n = internal_node->n_elt(); + + for (; ilookup_elt(i).child(); + + z = child_node->size(); + + if (i_tree < sum_z + z) { + /* continue search in i'th child of internal_node; + * accounting for the sum_z members in nodes to the left of i_child + */ + generic_node = child_node; + i_tree = i_tree - sum_z; + break; + } + + sum_z += z; + } + + if (i == n) { + throw std::runtime_error(tostr("BplusTree::find_ith: internal index failure", + xtag("i_tree", i_tree), + xtag("last_z", z), + xtag("n", internal_node->n_elt()), + xtag("sum_z", sum_z))); + } + } + break; + } /*switch*/ + + ++iter; + } /*loop over descending internal node path*/ + + throw std::runtime_error(tostr("BplusTree::find_ith: internal loop failure", + xtag("iter", iter))); + + /* impossible! */ + return cend; + } /*find_ith*/ + + static void node_clear_size(InternalNodeType * node) { + node->clear_size(); + } + + static void node_add_size(InternalNodeType * node, std::size_t incr_z) { + node->add_size(incr_z); + } + + static void node_sub_size(InternalNodeType * node, std::size_t decr_z) { + node->sub_size(decr_z); + } + + static void post_modify_add_ancestor_size(InternalNodeType * node, std::size_t incr_z, bool debug_flag) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag)); + + while (node) { + log && log(xtag("node", node), + xtag("old_z", node->size()), + xtag("incr_z", incr_z)); + + node->add_size(incr_z); + + node = node->parent(); + } + } /*post_modify_add_ancestor_size*/ + + static void post_modify_sub_ancestor_size(InternalNodeType * node, std::size_t decr_z, bool debug_flag) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag)); + + while (node) { + log && log(xtag("node", node), + xtag("old_z", node->size()), + xtag("decr_z", decr_z)); + + node->sub_size(decr_z); + + node = node->parent(); + } + } /*post_modify_sub_ancestor_size*/ + }; + + template + struct BplusTreeUtil { + public: + using GenericNodeType = GenericNode; + using InternalNodeType = InternalNode; + using LeafNodeType = LeafNode; + using const_iterator = detail::ConstIterator; + + static std::size_t get_node_size(GenericNodeType const * node) { return 0; } + + static void print_node_size(std::ostream & os, GenericNodeType const * node) { + os << "n/a"; + } + + /* find_ith not implemented without ordinal feature */ + static const_iterator find_ith(GenericNodeType * generic_node, + std::size_t i_tree, + const_iterator cend) { + throw std::runtime_error("BplusTreeUtil::find_ith: not implemented (requires tags::ordinal_enabled)"); + } + + /* per-node size not implemented, so these are no-ops */ + static void node_clear_size(InternalNodeType * node) {} + static void node_add_size(InternalNodeType * node, std::size_t incr_z) {} + static void node_sub_size(InternalNodeType * node, std::size_t decr_z) {} + static void post_modify_add_ancestor_size(InternalNodeType * node, std::size_t incr_z, bool debug_flag) {} + static void post_modify_sub_ancestor_size(InternalNodeType * node, std::size_t decr_z, bool debug_flag) {} + }; + } /*namespace tree*/ +} /*namespace xo*/ + +namespace logutil { + template + struct nodesize { + explicit nodesize(Node const * x) : node_{x} {} + + Node const * node() const { return node_; } + + private: + Node const * node_ = nullptr; + }; /*nodesize*/ + + template + inline std::ostream & operator<<(std::ostream & os, + nodesize const & x) { + xo::tree::BplusTreeUtil::print_node_size(os, x.node()); + return os; + }; +} /*namespace logutil*/ + +/* end BplusTreeUtil.hpp */ diff --git a/include/xo/tree/bplustree/GenericNode.hpp b/include/xo/tree/bplustree/GenericNode.hpp new file mode 100644 index 00000000..aa760c45 --- /dev/null +++ b/include/xo/tree/bplustree/GenericNode.hpp @@ -0,0 +1,123 @@ +/* @file GenericNode.hpp */ + +#pragma once + +#include "BplusTreeUtil.hpp" +#include "bplustree_tags.hpp" +#include // for std::unique_ptr +#include + +namespace xo { + namespace tree { + /* shim so we can partially specialize */ + template + struct GenericNodeBase { + }; /*GenericNodeBase*/ + + template + struct GenericNodeBase { + /* #of items (key-value pairs) in this subtree */ + virtual std::size_t size() const = 0; + }; /*GenericNodeShim*/ + + // ----- GenericNode ----- + // + // base class for LeafNode, InternalNode + + template + class GenericNode : public GenericNodeBase { + public: + using PropertiesType = Properties; + using InternalNodeType = InternalNode; + using LeafNodeType = LeafNode; + + public: + explicit GenericNode(NodeType ntype, std::size_t branching_factor) + : node_type_{ntype}, branching_factor_{branching_factor} {} + virtual ~GenericNode() = default; + + NodeType node_type() const { return node_type_; } + InternalNodeType * parent() const { return parent_; } + std::size_t n_elt() const { return n_elt_; } + std::size_t branching_factor() const { return branching_factor_; } + + void set_parent(InternalNodeType * x) { this->parent_ = x; } + +#ifdef OBSOLETE + /* #of items (key-value pairs) in this subtree */ + virtual std::size_t size() const = 0; +#endif + + virtual Key const & glb_key() const = 0; + /* support methods for BplusTree::verify() + * with_lub. true to use lub_key; false to ignore + * lub_key. if with_lub=true, strict least upper bound key (in B+ tree) for this subtree; + * all keys in this subtree must be strictly less than lub_key. + * ignored when with_lub=false + * lh_leaf. if null, this subtree contains the smallest key in ancestor B+ tree; + * if non-null, lh_leaf's rightmost key is immediate predecessor + * of leftmost key in this subtree + * rh_leaf. if null, this subtree contains the largest key in ancestor B+ tree; + * if non-null, rh_leaf's leftmost key is immediate successor + * of rightmost key in this subtree + */ + virtual std::size_t verify_helper(InternalNodeType const * parent, + bool with_lub, + Key const & lub_key, + LeafNodeType const * lh_leaf, + LeafNodeType const * rh_leaf) const = 0; + virtual void verify_glb_key(Key const & key) const = 0; + FindNodeResult c_find_min_leaf_node() const; + FindNodeResult c_find_max_leaf_node() const; + + virtual FindNodeResult find_min_leaf_node() = 0; + virtual FindNodeResult find_max_leaf_node() = 0; + + /* notification just before permanently removing this node from B+ tree */ + virtual void notify_remove() {} + + private: + /* NodeType::internal | NodeType::leaf */ + NodeType node_type_; + /* pointer to parent node + * invariant: parent has direct pointer to this node, + * except briefly during construction + */ + InternalNodeType * parent_ = nullptr; + + protected: + /* #of non-empty elements (children) of this node + * + * invariant: + * - .elt_v[i].child.ptr is non-null for 0 <= i < .n_elt + * - for (0 < i < .n_elt): + * .elt_v[i-1].key < .elt_v[i].key + * - elt_v[i].key not defined for (i >= .n_elt) + */ + std::size_t n_elt_ = 0; + /* need to store actual branching factor, for LeafNode/InternalNode dtors */ + std::size_t branching_factor_ = 0; + }; /*GenericNode*/ + + /* const version (non-const version below) */ + template + FindNodeResult const> + GenericNode::c_find_min_leaf_node() const { + InternalNode * self = const_cast *>(this); + + return self->find_min_leaf_node(); + } /*c_find_min_leaf_node*/ + + /* const version (non-const version below) */ + template + FindNodeResult const> + GenericNode::c_find_max_leaf_node() const { + InternalNode * self = const_cast *>(this); + + return self->find_max_leaf_node(); + } /*c_find_max_leaf_node*/ + + } /*namespace tree*/ +} /*namespace xo*/ + +/* end GenericNode.hpp */ diff --git a/include/xo/tree/bplustree/InternalNode.hpp b/include/xo/tree/bplustree/InternalNode.hpp new file mode 100644 index 00000000..d4dce90a --- /dev/null +++ b/include/xo/tree/bplustree/InternalNode.hpp @@ -0,0 +1,768 @@ +/* @file InternalNode.hpp */ + +#pragma once + +#include "GenericNode.hpp" +#include "indentlog/scope.hpp" +#include "indentlog/print/tostr.hpp" +#include + +namespace xo { + namespace tree { + // ----- InternalNodeItem ------ + + /* see also: NodeItem */ + template + struct NodeItem { + using GenericNodeType = GenericNode; + + public: + NodeItem() = default; + explicit NodeItem(std::unique_ptr child) + : child_{std::move(child)} { + if (child_) + this->key_ = child_->glb_key(); + } + + Key const & key() const { return key_; } + GenericNodeType * child() const { return child_.get(); } + + std::unique_ptr release_child() { return std::move(child_); } + + void set_key(Key key) { key_ = std::move(key); } + + void notify_remove() { + if (child_) + child_->notify_remove(); + } /*notify_remove*/ + + private: + /* invariant: .key is leftmost key in subtree rooted at .child + * (i.e. greatest lower bound for keys in that subtree) + */ + Key key_; + /* subtree. subtree has minimum key value .key */ + std::unique_ptr child_; + }; /*NodeItem */ + + template + using InternalNodeItem = NodeItem; + + /* struct with same size as InternalNodeItem, but POD + with no ctor/dtor */ + template + using InternalNodeItemPlaceholder = NodeItemPlaceholder; + + /* default implements tags::ordinal_disabled; see partial specialization below for ordinal_enabled */ + template + struct InternalNodeShim : public GenericNode { + public: + using GenericNodeType = GenericNode; + + public: + InternalNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode{ntype, branching_factor} {} + + protected: + /* not implemented with tags::ordinal_disabled */ + void assign_size(std::size_t z) {} + }; + + template + struct InternalNodeShim : public GenericNode { + public: + using GenericNodeType = GenericNode; + + public: + InternalNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode{ntype, branching_factor} {} + + void clear_size() { this->size_ = 0; } + void add_size(std::size_t z) { this->size_ += z; } + void sub_size(std::size_t z) { this->size_ -= z; } + + virtual std::size_t size() const override { return size_; } + + protected: + void assign_size(std::size_t z) { this->size_ = z; } + + protected: + std::size_t size_ = 0; + }; /*InternalNodeShim*/ + + /* require: + * - Properties.branching_factor() + */ + template + struct InternalNode : public InternalNodeShim { + public: + using GenericNodeType = GenericNode; + using InternalNodeType = InternalNode; + using LeafNodeType = LeafNode; + using InternalNodeItemPlaceholderType = InternalNodeItemPlaceholder; + using InternalNodeItemType = InternalNodeItem; + + public: + virtual ~InternalNode(); + + /* node size in bytes (increases with branching factor) */ + static std::size_t node_sizeof(std::size_t branching_factor); + + /* use when splitting root node for the first time; + * new root node will be leaf->internal. + * + * require: child_1, child_2 are non-empty + */ + static std::unique_ptr make_2(std::unique_ptr child_1, + std::unique_ptr child_2); + + /* Before: + * + * m = mid_ix + * n = src.n_elt - 1 + * xa @ [m-1] + * xb @ [m] + * xz @ [n-1] + * + * src.elt_v[] + * + * 0 m-1 m n-1 + * +----+-...-+----+----+-...-+----+ + * | x0 | ... | xa | xb | ... | xz | + * +----+-...-+----+----+-...-+----+ + * + * <----------- n items -----------> + * + * After: + * + * src.elt_v[] new_node.elt_v[] + * + * n-m-1 + * 0 m-1 0 v + * +----+-...-+----+ +----+-...-+----+ + * | x0 | ... | xa | | xb | | xz | + * +----+-...-+----+ +----+-...-+----+ + * + * <--- m items ---> <-- n-m items --> + */ + static std::unique_ptr annex(std::size_t mid_ix, + InternalNode * src); + + /* .elt_v[] + * + * 0 k n-1 with: n <= b = branching factor + * +---+---+- ... -+---+- ... -+---+---+ k = lub(key) in {e1..en} + * | e1| e2| | ek| | | en| + * +---+---+- ... -+---+- ... -+---+---+ + * + * retval.first: true if key already present in tree. implies lub_ix_recd.second >= 1 + * retval.second: upper bound (strict) index position in .elt_v[] of key + * + * Cost: O(log(bf)) key comparisons + */ + std::size_t find_lub_ix(Key const & key) const; + + /* warning: requires key is present! */ + std::size_t find_ix(Key const & key) const { return this->find_lub_ix(key) - 1; } + + /* O(bf), but does not rely on key invariants. */ + std::size_t locate_child_by_address(GenericNodeType const * target_child) const; + + InternalNodeItemType & lookup_elt(std::size_t i) { return *(reinterpret_cast(&(elt_v_[i]))); } + + InternalNodeItemType const & lookup_elt(std::size_t i) const { return *(reinterpret_cast(&(elt_v_[i]))); } + + FindNodeResult find_child(Key const & key); + + /* insert node at position ix; moving items starting in .elt_v[ix] one slot to the right */ + void insert_node(std::size_t ix, std::unique_ptr child, bool debug_flag); + + /* remove node at position ix; moving items starting .elt_v[ix+1] one slot to the left; + * if target is a leaf node, also remove from prev_leafnode/next_leafnode list + */ + void remove_node(std::size_t ix, bool debug_flag); + + /* redistribute last n items from left-hand sibling lh to this internal node */ + void prepend_from_lh_sibling(InternalNode * lh, std::size_t n, bool debug_flag); + + /* redistribute first n items from right-hand sibling rh to this internal node */ + void append_from_rh_sibling(std::size_t n, InternalNode * rh); + + void append_rh_sibling(InternalNode * rh) { this->append_from_rh_sibling(rh->n_elt(), rh); } + + /* returns new node with upper half of original element vector (i.e. of this.elt_v[]); + * original updated to retain lower half + */ + std::unique_ptr split_internal(); + + void set_glb_key(Key key) { this->lookup_elt(0).set_key(key); } + + /* memory for InternalNode instances is always created using new[], + * so required to use delete[] to deallocate + */ + void operator delete (void * mem) noexcept { ::operator delete[](mem); } + + // ----- inherited from GenericNode ----- + + virtual Key const & glb_key() const override { return this->lookup_elt(0).key(); } + + virtual std::size_t verify_helper(InternalNode const * parent, + bool with_lub_flag, + Key const & lub_key, + LeafNodeType const * lh_leaf, + LeafNodeType const * rh_leaf) const override; + + virtual void verify_glb_key(Key const & key) const override; + + /* find in subtree_arg the leftmost leaf node (i.e. leaf node with smallest key) */ + virtual FindNodeResult find_min_leaf_node() override; + /* find in subtree_arg the rightmost leaf node (i.e. leaf node with largest key) */ + virtual FindNodeResult find_max_leaf_node() override; + + private: + explicit InternalNode(std::size_t branching_factor); + + private: +#ifdef OBSOLETE + /* total #of elements in this subtree */ + std::size_t size_ = 0; +#endif + /* flexible array; actual size will be .branching_factor(). + * + * .elt_v[i] is created/destroyed as an InternalNodeItemType with non-trivial ctor/dtor. + * we must declare member using POD placeholder to satisfy flexible array rules + * + * invariant: + * - with branching factor b, so range for .elt_v[] is 0 .. b-1: + * - .elt_v[j].child.ptr is null -> {.elt_v[j+1].child.ptr .. .elt_v[b-1].child.ptr} are also null + */ + InternalNodeItemPlaceholderType elt_v_[]; + }; /*InternalNode*/ + + template + InternalNode::~InternalNode() { + /* since we're using flexible array for .elt_v[], need to manually run destructors */ + for (std::size_t i=0, n=this->branching_factor_; ilookup_elt(i).~InternalNodeItemType(); + } + + /* hygiene */ + BplusTreeUtil::node_clear_size(this); + this->n_elt_ = 0; + this->branching_factor_ = 0; + } /*dtor*/ + + template + std::size_t + InternalNode::node_sizeof(std::size_t branching_factor) { + return (sizeof(InternalNode) + + (branching_factor + * sizeof(InternalNodeItemType))); + } /*node_sizeof*/ + + template + std::unique_ptr> + InternalNode::make_2(std::unique_ptr child_1, + std::unique_ptr child_2) { + std::size_t branching_factor = child_1->branching_factor(); + + std::size_t mem_z = node_sizeof(branching_factor); + std::uint8_t * mem = new std::uint8_t[mem_z]; + + assert(child_1->n_elt() > 0); + assert(child_2->n_elt() > 0); + + std::unique_ptr retval(new (mem) InternalNode(branching_factor)); + + child_1->set_parent(retval.get()); + child_2->set_parent(retval.get()); + + retval->assign_size(BplusTreeUtil::get_node_size(child_1.get()) + + BplusTreeUtil::get_node_size(child_2.get())); + retval->n_elt_ = 2; + + retval->lookup_elt(0) = std::move(InternalNodeItemType(std::move(child_1))); + retval->lookup_elt(1) = std::move(InternalNodeItemType(std::move(child_2))); + + return retval; + } /*make_2*/ + + template + std::unique_ptr> + InternalNode::annex(std::size_t mid_ix, + InternalNode * src) + { + std::size_t branching_factor = src->branching_factor(); + + std::size_t mem_z = node_sizeof(branching_factor); + std::uint8_t * mem = new std::uint8_t[mem_z]; + + std::unique_ptr new_node(new (mem) InternalNode(branching_factor)); + + std::size_t hi_ix = src->n_elt(); + + new_node->n_elt_ = hi_ix - mid_ix; + + std::size_t annex_z = 0; + + /* annexing upper-half of *src into new_node */ + for (std::size_t i = 0, n = hi_ix - mid_ix; i < n; ++i) { + InternalNodeItemType & src_slot = src->lookup_elt(mid_ix + i); + InternalNodeItemType & new_slot = new_node->lookup_elt(i); + + annex_z += BplusTreeUtil::get_node_size(src_slot.child()); + + new_slot = std::move(src->lookup_elt(mid_ix + i)); + new_slot.child()->set_parent(new_node.get()); + } + + new_node->assign_size(annex_z); + + /* ordinal_disabled: noop + * ordinal_enabled: bookkeeping for src.size (+ new_node.size, see above) + */ + src->assign_size(BplusTreeUtil::get_node_size(src) - annex_z); + src->n_elt_ = mid_ix; + + return new_node; + } /*annex*/ + + template + std::size_t + InternalNode::find_lub_ix(Key const & key) const { + if (key < this->lookup_elt(0).key()) + return 0; + + std::size_t lo = 0; + std::size_t hi = this->n_elt_; + + while (lo + 1 < hi) { + std::size_t mid = lo + (hi - lo) / 2; + + if (key < this->lookup_elt(mid).key()) + hi = mid; + else + lo = mid; + } + + return hi; + } /*find_lub_ix*/ + + template + std::size_t + InternalNode::locate_child_by_address(GenericNodeType const * target_child) const { + for (std::size_t ix = 0; ix < this->n_elt_; ++ix) { + if (this->lookup_elt(ix).child() == target_child) + return ix; + } + + return static_cast(-1); + } /*locate_child_by_address*/ + + template + FindNodeResult> + InternalNode::find_min_leaf_node() { + FindNodeResult findresult(0, this); + + while (findresult.node() && (findresult.node()->node_type() == NodeType::internal)) { + std::size_t min_ix = 0; + + findresult = FindNodeResult(min_ix, + (reinterpret_cast(findresult.node())) + ->lookup_elt(min_ix /*leftmost child*/).child()); + } + + /* findresult.node()->node_type() == NodeType::leaf (if non-null) */ + + if (!findresult.node()) { + assert(false); + return FindNodeResult(); + } + + assert(findresult.node()->node_type() == NodeType::leaf); + + return FindNodeResult(findresult.ix(), + reinterpret_cast(findresult.node())); + } /*find_min_leaf_node*/ + + template + FindNodeResult> + InternalNode::find_max_leaf_node() { + FindNodeResult findresult(0, this); + + while (findresult.node() && (findresult.node()->node_type() == NodeType::internal)) { + std::size_t max_ix = findresult.node()->n_elt() - 1; + + findresult = FindNodeResult + (max_ix, + (reinterpret_cast(findresult.node())) + ->lookup_elt(max_ix /*rightmost child*/).child()); + } + + /* findresult.node()->node_type() == NodeType::leaf (if non-null) */ + + if (!findresult.node()) { + assert(false); + return FindNodeResult(); + } + + assert(findresult.node()->node_type() == NodeType::leaf); + + return FindNodeResult(findresult.ix(), + reinterpret_cast(findresult.node())); + } /*find_max_leaf_node*/ + + template + FindNodeResult> + InternalNode::find_child(Key const & key) { + std::size_t lub_ix = this->find_lub_ix(key); + + if (lub_ix > 0) + --lub_ix; + + return FindNodeResult(lub_ix, this->lookup_elt(lub_ix).child()); + } /*find_child*/ + + template + void + InternalNode::insert_node(std::size_t ix, std::unique_ptr child, bool debug_flag) + { + using xo::scope; + using xo::tostr; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag), + xtag("self", this), + xtag("n_elt", this->n_elt()), + xtag("bf", this->branching_factor()), + xtag("ix", ix), + xtag("child", child.get())); + + if (this->n_elt_ >= this->branching_factor()) { + assert(false); + throw std::runtime_error(tostr("InternalNode::insert_node: node already full", + xtag("node.n_elt", this->n_elt()), + xtag("branching_factor", this->branching_factor()))); + } + + if (ix > this->n_elt_) { + assert(false); + throw std::runtime_error(tostr("InternalNode::insert_node: insert position out of range", + xtag("ix", ix), + xtag("node.n_elt", this->n_elt()), + xtag("bf", this->branching_factor()))); + } + + std::size_t pos_ix = this->n_elt_; + + while (pos_ix > ix) { + this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix - 1)); + --pos_ix; + } + + /* WARNING: don't update .size here + * in practice we use .insert_node() when introducing a single new key/value pair; + * when we use .insert_node() we split an existing node, + * and actually just want to increment .size. + * + * We leave this to caller (e.g. BplusTree.internal_insert_aux()) + * because in that context can see the upstream split + */ + // this->size_ += child->n_elt(); + + ++(this->n_elt_); + child->set_parent(this); + this->lookup_elt(ix) = InternalNodeItemType(std::move(child)); + } /*insert_node*/ + + template + void + InternalNode::remove_node(std::size_t ix, bool debug_flag) { + using xo::scope; + using xo::tostr; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag), + xtag("self", this), + xtag("n_elt", this->n_elt()), + xtag("bf", this->branching_factor()), + xtag("ix", ix)); + + if (ix >= this->n_elt_) { + assert(false); + throw std::runtime_error(tostr("InternalNode::remove_node: target position out of range", + xtag("ix", ix), + xtag("node.n_elt", this->n_elt()), + xtag("bf", this->branching_factor()))); + } + + std::size_t pos_ix = ix; + std::size_t end_ix = this->n_elt_ - 1; + + { + InternalNodeItemType & target_item = this->lookup_elt(pos_ix); + + /* WARNING: don't update .size here + * in practice we use .remove_node() when deleting a single new key/value pair; + * when we use .remove_node() we merge existing nodes, + * and actually just want to decrement .size. + * + * We leave this to caller (e.g. BplusTree.internal_remove_aux()) + * because in that context can see the upstream merge + */ + //this->size_ -= target_item.child()->size(); + target_item.notify_remove(); + } + + while (pos_ix < end_ix) { + //scope x1("loop", debug_flag); + //x1(xtag("pos_ix", pos_ix)); + + this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix + 1)); + ++pos_ix; + } + + --(this->n_elt_); + } /*remove_node*/ + + template + void + InternalNode::prepend_from_lh_sibling(InternalNode * lh, std::size_t n, bool debug_flag) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag), + xtag("@", this), xtag("n", n)); + + if (this->n_elt() + n > this->branching_factor()) { + assert(false); + throw std::runtime_error(tostr("InternalNode.prepend_from_lh_sibling: expected combined #elt <= bf", + xtag("self.n_elt", this->n_elt()), + xtag("n", n), + xtag("bf", this->branching_factor()))); + } + + std::size_t n_lh = lh->n_elt(); + std::size_t n_rh = this->n_elt(); + + /* move elts in *this to the right n steps (starting from the end) */ + for (std::size_t ixp1 = this->n_elt(); ixp1 > 0; --ixp1) { + std::size_t ix = ixp1 - 1; + //x.log("move", xtag("ix", ix), xtag("ix+n", ix+n)); + this->lookup_elt(ix + n) = std::move(this->lookup_elt(ix)); + } + + std::size_t xfer_z = 0; + + /* xfer n elts from upper end of lh, to lower end of *this */ + for (std::size_t ix = 0; ix < n; ++ix) { + //x.log("fill", xtag("ix", ix), xtag("n_lh-n+ix", n_lh - n + ix)); + + InternalNodeItemType & lh_sibling_item = lh->lookup_elt(n_lh - n + ix); + + xfer_z += BplusTreeUtil::get_node_size(lh_sibling_item.child()); + + this->lookup_elt(ix) = std::move(lh_sibling_item); + /* + fixup parent pointer */ + this->lookup_elt(ix).child()->set_parent(this); + } + + BplusTreeUtil::node_add_size(this, xfer_z); + BplusTreeUtil::node_sub_size(lh, xfer_z); + + this->n_elt_ += n; + lh->n_elt_ -= n; + + log && log(xtag("this.glb_key", this->glb_key()), + xtag("this[0].key", this->lookup_elt(0).key())); + + log.end_scope(); + } /*prepend_from_lh_sibling*/ + + template + void + InternalNode::append_from_rh_sibling(std::size_t n, InternalNode * rh) { + using xo::xtag; + + if (this->n_elt() + n > this->branching_factor()) { + assert(false); + throw std::runtime_error(tostr("InternalNode.append_from_rh_sibling: expected combined #elt <= bf", + xtag("self.n_elt", this->n_elt()), + xtag("n", n), + xtag("bf", this->branching_factor()))); + } + + std::size_t n_lh = this->n_elt(); + std::size_t xfer_z = 0; + + for (std::size_t ix = 0; ix < n; ++ix) { + InternalNodeItemType & rh_sibling_item = rh->lookup_elt(ix); + + xfer_z += BplusTreeUtil::get_node_size(rh_sibling_item.child()); + this->lookup_elt(n_lh + ix) = std::move(rh_sibling_item); + /* + fixup parent pointer */ + this->lookup_elt(n_lh + ix).child()->set_parent(this); + } + + BplusTreeUtil::node_add_size(this, xfer_z); + this->n_elt_ += n; + + /* shuffle remaining members of rh sibling n items to the left */ + for (std::size_t ix = 0; ix < rh->n_elt() - n; ++ix) { + rh->lookup_elt(ix) = std::move(rh->lookup_elt(ix + n)); + } + + BplusTreeUtil::node_sub_size(rh, xfer_z); + rh->n_elt_ -= n; + } /*append_from_rh_sibling*/ + + template + std::unique_ptr> + InternalNode::split_internal() { + std::size_t n_elt = this->n_elt_; + std::size_t mid_ix = n_elt / 2; + + return InternalNode::annex(mid_ix, this); + } /*split_internal*/ + + template + std::size_t + InternalNode::verify_helper(InternalNode const * parent, + bool with_lub_flag, + Key const & lub_key, + LeafNodeType const * lh_leaf, + LeafNodeType const * rh_leaf) const + { + using xo::tostr; + using xo::xtag; + + std::size_t retval = 0; + + /* verify immediate parent pointer is correct */ + if (this->parent() != parent) { + throw std::runtime_error(tostr("InternalNode::verify_helper" + ": expected parent pointer to refer to actual parent", + xtag("stored_parent", this->parent()), + xtag("actual_parent", parent))); + } + + std::size_t n = this->n_elt_; + + /* verify all children have same NodeType (either all= internal or all= leaf) */ + NodeType target_child_node_type = NodeType::leaf; + + if (n > 0) + target_child_node_type = this->lookup_elt(0).child()->node_type(); + + LeafNodeType const * prev_lh_leaf = lh_leaf; + + for (std::size_t i=0; i < n; ++i) { + /* check consistent node type */ + NodeType i_nodetype = this->lookup_elt(i).child()->node_type(); + + if ((i > 0) && (i_nodetype != target_child_node_type)) { + throw std::runtime_error(tostr("InternalNode::verify_helper" + ": expected all children to share the same node type", + xtag("i", i), + xtag("elt[0].node_type", target_child_node_type), + xtag("elt[i].node_type", i_nodetype))); + } + + /* nested verify on child subtrees */ + InternalNodeItemType const & i_elt = this->lookup_elt(i); + + LeafNodeType const * next_lh_leaf = ((i+1 < n) + ? this->lookup_elt(i+1).child()->find_min_leaf_node().node() + : rh_leaf); + + retval += i_elt.child()->verify_helper(this, + (i+1 < n) ? true : with_lub_flag, + (i+1 < n) ? this->lookup_elt(i+1).key() : lub_key, + prev_lh_leaf, + next_lh_leaf); + + prev_lh_leaf = i_elt.child()->find_max_leaf_node().node(); + } + + if (Properties::ordinal_tag_value() == tags::ordinal_enabled) { + /* verify stored subtree size is consistent with children's */ + std::size_t sum_z = 0; + + for (std::size_t i=0, n=this->n_elt_; i < n; ++i) { + InternalNodeItemType const & elt = this->lookup_elt(i); + + sum_z += BplusTreeUtil::get_node_size(elt.child()); + } + + std::size_t self_z = BplusTreeUtil::get_node_size(this); + + if (sum_z != self_z) { + throw std::runtime_error(tostr("InternalNode::verify_helper", + ": inconsistent subtree size", + xtag("node", this), + xtag("treez[stored]", self_z), + xtag("treez[computed]", sum_z))); + } + } + + /* verify stored glb_key is correct */ + for (std::size_t i=0, n=this->n_elt_; i < n; ++i) { + InternalNodeItemType const & elt = this->lookup_elt(i); + + elt.child()->verify_glb_key(elt.key()); + } + + /* verify locally stored keys appear in sorted order */ + for (std::size_t i=1; i < n; ++i) { + InternalNodeItemType const & prev = this->lookup_elt(i-1); + InternalNodeItemType const & elt = this->lookup_elt(i); + + if (prev.key() < elt.key()) { + ; + } else { + throw std::runtime_error(tostr("InternalNode::verify_helper" + ": expected local keys in strictly increasing order", + xtag("i", i), + xtag("key(i-1)", prev.key()), + xtag("key(i)", elt.key()))); + } + } + + /* verify highest stored key before parent-supplied upper bound */ + if (with_lub_flag) { + if (this->lookup_elt(n-1).key() < lub_key) { + ; + } else { + throw std::runtime_error(tostr("InternalNode::verify_helper" + ": expected highest local key before parent-supplied lub key", + xtag("n", n), + xtag("key(n-1)", this->lookup_elt(n-1).key()), + xtag("lub_key", lub_key))); + } + } + + return retval; + } /*verify_helper*/ + + template + void + InternalNode::verify_glb_key(Key const & key) const { + InternalNodeItemType const & elt = this->lookup_elt(0); + + elt.child()->verify_glb_key(key); + } /*verify_glb_key*/ + + template + InternalNode::InternalNode(std::size_t branching_factor) + : InternalNodeShim{NodeType::internal, branching_factor} + { + /* must invoke ctor explicitly for each .elt_v[i]. + * compiler doesn't know extent of .elt_v[], since it's a flexible array + */ + for (std::size_t i = 0; i < branching_factor; ++i) { + /* using placement new to force ctor call inside flexible array */ + new (&(this->lookup_elt(i))) InternalNodeItemType(); + } + } /*ctor*/ + + } /*namespace tree*/ +} /*namespace xo*/ + +/* end InternalNode.hpp */ diff --git a/include/xo/tree/bplustree/Iterator.hpp b/include/xo/tree/bplustree/Iterator.hpp new file mode 100644 index 00000000..90f57924 --- /dev/null +++ b/include/xo/tree/bplustree/Iterator.hpp @@ -0,0 +1,355 @@ +/* @file Iterator.hpp */ + +#pragma once + +#include "IteratorUtil.hpp" +#include "LeafNode.hpp" +#include "indentlog/print/tostr.hpp" + +namespace xo { + namespace tree { + namespace detail { + /* TODO: move to tree/IteratorUtil.hpp */ + + /* placeholder - specialize on isConst */ + template + struct NodeTypeTraits { using LeafNodeType = void; }; + + /* non-const node pointer */ + template + struct NodeTypeTraits { + using NativeLeafNodeType = LeafNode; + using LeafNodeType = NativeLeafNodeType; + using NativeContentsType = typename LeafNodeType::ContentsType; + using LeafNodePtrType = LeafNodeType *; + }; + + /* const node pointer */ + template + struct NodeTypeTraits { + using NativeLeafNodeType = LeafNode; + using LeafNodeType = NativeLeafNodeType const; + using NativeContentsType = typename LeafNodeType::ContentsType const; + using LeafNodePtrType = LeafNodeType const *; + }; + + /* shared between const and non-const b+ tree iterators + * + * +------------+ + * |IteratorBase| + * | .dirn | + * | .location | + * | .leafnode | + * | .ix | + * +------------+ + * ^ + * | isa +-------------+ + * +-----------|ConstIterator| + * | | .operator++ | + * | | .operator-- | + * | +-------------+ + * | + * | isa +--------+ + * +-----------|Iterator| + * +--------+ + */ + template + class IteratorBase { + public: + using Traits = NodeTypeTraits; + using BpLeafNodePtrType = typename Traits::LeafNodePtrType; + using BpLeafNodeItemType = typename Traits::LeafNodeType::LeafNodeItemType; + using NativeContentsType = typename Traits::NativeContentsType; + + protected: + IteratorBase() = default; + IteratorBase(IteratorDirection dirn, IteratorLocation loc, BpLeafNodePtrType leaf, std::size_t ix) + : dirn_{dirn}, location_{loc}, leafnode_{leaf}, ix_{ix} {} + IteratorBase(IteratorBase const &) = default; + + static IteratorBase prebegin_aux(BpLeafNodePtrType node) { + return IteratorBase(ID_Forward, IL_BeforeBegin, node, 0 /*ix*/); + } + + static IteratorBase begin_aux(BpLeafNodePtrType node) { + return IteratorBase(ID_Forward, + (node ? IL_Regular : IL_AfterEnd), + node, + 0 /*ix*/); + } + + static IteratorBase end_aux(BpLeafNodePtrType node) { + return IteratorBase(ID_Forward, + IL_AfterEnd, + node, + 0 /*ix*/); + } + + static IteratorBase rprebegin_aux(BpLeafNodePtrType node) { + return IteratorBase(ID_Reverse, + IL_AfterEnd, + node, + 0 /*ix*/); + } + + static IteratorBase rbegin_aux(BpLeafNodePtrType node) { + return IteratorBase(ID_Reverse, + (node ? IL_Regular : IL_BeforeBegin), + node, + (node ? node->n_elt() - 1: 0)); + } + + static IteratorBase rend_aux(BpLeafNodePtrType node) { + return IteratorBase(ID_Reverse, + IL_BeforeBegin, + node, + 0 /*ix*/); + } + + public: + IteratorLocation location() const { return location_; } + BpLeafNodePtrType node() const { return leafnode_; } + BpLeafNodeItemType const * item_addr() const { return &(leafnode_->lookup_elt(this->ix_)); } + + NativeContentsType const & operator*() const { + this->check_regular(); + return this->leafnode_->lookup_elt(this->ix_).kv_pair(); + } /*operator**/ + + NativeContentsType const * operator->() const { + return &(this->operator*()); + } /*operator->*/ + + bool is_sentinel() const { return (this->location_ != IL_Regular); } + bool is_dereferenceable() const { return !this->is_sentinel(); } + + operator bool() const { return this->is_deferenceable(); } + + bool operator==(IteratorBase const & x) const { + return (this->location_ == x.location_) && (this->leafnode_ == x.leafnode_) && (this->ix_ == x.ix_); + } + + bool operator!=(IteratorBase const & x) const { + return (this->location_ != x.location_) || (this->leafnode_ != x.leafnode_) || (this->ix_ != x.ix_); + } + + void print(std::ostream & os) const { + using xo::xtag; + + os << ""; + } /*print*/ + + /* pre-increment */ + IteratorBase & operator++() { + return ((this->dirn_ == ID_Forward) + ? this->next_step() + : this->prev_step()); + } /*operator++*/ + + /* pre-decrement */ + IteratorBase & operator--() { + return ((this->dirn_ == ID_Forward) + ? this->prev_step() + : this->next_step()); + } /*operator--*/ + + private: + IteratorBase & next_step() { + switch(this->location_) { + case IL_BeforeBegin: + /* .leafnode is leftmost node in tree */ + this->location_ = IL_Regular; + break; + case IL_Regular: + { + /* #of elts in node, not #of elts in tree! */ + std::size_t n_elt = this->leafnode_->n_elt(); + + if (this->ix_ + 1 < n_elt) { + ++(this->ix_); + } else if (this->leafnode_->next_leafnode()) { + this->leafnode_ = this->leafnode_->next_leafnode(); + this->ix_ = 0; + } else { + /* preserve .leafnode: + * (a) for == comparison w/ .end() iterator + * (b) so we can iterate backwards from end position + */ + //this->leafnode_ = this->leafnode_->next_leafnode(); + this->location_ = IL_AfterEnd; + this->ix_ = 0; + } + } + break; + case IL_AfterEnd: + break; + } + + return *this; + } /*next_step*/ + + IteratorBase & prev_step() { + switch(this->location_) { + case IL_BeforeBegin: + break; + case IL_Regular: + if (this->ix_ > 0) { + --(this->ix_); + } else if (this->leafnode_->prev_leafnode()) { + this->leafnode_ = this->leafnode_->prev_leafnode(); + this->ix_ = this->leafnode_->n_elt() - 1; + } else /* .ix == 0 && .leafnode.prev_leafnode == nullptr */ { + /* preserve .leafnode: + * (a) for == comparison w/ .prebegin() iterator + * (b) so iterator is reversible; can iterate forwards from prebegin position + */ + this->location_ = IL_BeforeBegin; + } + break; + case IL_AfterEnd: + /* .leafnode is rightmost node in tree */ + this->location_ = IL_Regular; + this->ix_ = this->leafnode_->n_elt() - 1; + break; + } + + return *this; + } /*prev_step*/ + + private: + void check_regular() const { + using xo::tostr; + using xo::xtag; + + if (this->location_ != IL_Regular) { + throw std::runtime_error(tostr("bplustree iterator: cannot deref iterator" + " in sentinel state", + xtag("loc", this->location_))); + } + } /*check_regular*/ + + private: + /* ID_Forward forward iterator + * ID_Reverse reverse iterator + */ + IteratorDirection dirn_ = ID_Forward; + /* IL_BeforeBegin | IL_Regular | IL_AfterEnd + * + * operator++ operator++ + * IL_BeforeBegin --------------> IL_Regular --------------> IL_AfterEnd + * /-> -\ + * | | + * \----------------/ + * operator++ + * + * operator-- operator-- + * IL_BeforeBegin <------------- IL_Regular <-------------- IL_AfterEnd + * /-- <-\ + * | | + * \-----------------/ + * operator-- + * + * + */ + IteratorLocation location_ = IL_AfterEnd; + /* .location .leafnode + * IL_BeforeBegin BplusTree.leafnode_begin (leftmost leaf node) + * IL_Regular any leaf node reachable from BplusTree.leafnode_begin + * (or equivalently from BplusTree.leafnode_end) + * IL_AfterEnd BplusTree.leafnode_end (rightmost leaf node) + */ + BpLeafNodePtrType leafnode_ = nullptr; + /* index position within .leafnode; + * 0 when .location is IL_BeforeBegin | IL_AfterEnd + */ + std::size_t ix_ = 0; + }; /*IteratorBase*/ + + template + class ConstIterator : public IteratorBase { + public: + using iterator_concept = std::bidirectional_iterator_tag; + + using BpIteratorBase = IteratorBase; + using BpLeafNodePtrType = typename BpIteratorBase::BpLeafNodePtrType; + + public: + ConstIterator() = default; + ConstIterator(IteratorDirection dirn, IteratorLocation loc, BpLeafNodePtrType leaf, std::size_t ix) + : IteratorBase(dirn, loc, leaf, ix) {} + ConstIterator(ConstIterator const & x) = default; + ConstIterator(BpIteratorBase const & x) : BpIteratorBase(x) {} + ConstIterator(BpIteratorBase && x) : BpIteratorBase{std::move(x)} {} + + static ConstIterator prebegin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::prebegin_aux(leaf); } + static ConstIterator begin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::begin_aux(leaf); } + static ConstIterator end_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::end_aux(leaf); } + + static ConstIterator rprebegin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::rprebegin_aux(leaf); } + static ConstIterator rbegin_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::rbegin_aux(leaf); } + static ConstIterator rend_aux(BpLeafNodePtrType leaf) { return BpIteratorBase::rend_aux(leaf); } + + /* pre-increment */ + ConstIterator & operator++() { + BpIteratorBase::operator++(); + return *this; + } /*operator++*/ + + /* post-increment */ + ConstIterator operator++(int) { + ConstIterator retval = *this; + + ++(*this); + + return retval; + } /*operator++*/ + + /* pre-decrement */ + ConstIterator & operator--() { + BpIteratorBase::operator--(); + return *this; + } /*operator--*/ + + /* post-decrement */ + ConstIterator operator--(int) { + ConstIterator retval = *this; + + --(*this); + + return retval; + } /*operator--*/ + }; /*ConstIterator*/ + } /*namespace detail*/ + + template + inline std::ostream & + operator<<(std::ostream & os, + detail::IteratorBase const & iter) + { + iter.print(os); + return os; + } /*operator<<*/ + } /*namespace tree*/ +} /*namespace xo*/ + +/* end Iterator.hpp */ diff --git a/include/xo/tree/bplustree/IteratorUtil.hpp b/include/xo/tree/bplustree/IteratorUtil.hpp new file mode 100644 index 00000000..0a0157af --- /dev/null +++ b/include/xo/tree/bplustree/IteratorUtil.hpp @@ -0,0 +1,56 @@ +/* @file IteratorUtil.hpp */ + +#pragma once + +#include + +namespace xo { + namespace tree { + namespace detail { + + enum IteratorDirection { + /* ID_Forward. forward iterator + * ID_Reverse. reverse iterator + */ + ID_Forward, + ID_Reverse + }; /*IteratorDirection*/ + + /* specify iterator location relative to a particular b+ tree node */ + enum IteratorLocation { + /* + * IL_BeforeBegin. if non-empty tree, Iterator.node is the first node + * in the tree (the one with smallest key), + * and iterator refers to the location + * "one before" that first node. + * IL_Regular. iterator refers to member of the tree + * given by Iterator.node + * IL_AfterEnd. if non-empty tree, Iterator.node is the last node + * in the tree (the one with largest key), + * and iterator refers the the location + * "one after" that last node. + */ + IL_BeforeBegin, + IL_Regular, + IL_AfterEnd + }; /*IteratorLocation*/ + + static inline char const * iterator_location_descr(IteratorLocation x) { + switch(x) { + case IL_BeforeBegin: return "before-begin"; + case IL_Regular: return "regular"; + case IL_AfterEnd: return "after-end"; + default: return "???"; + } + } /*iteerator_location_descr*/ + + inline std::ostream & + operator<<(std::ostream & os, IteratorLocation x) { + os << iterator_location_descr(x); + return os; + } /*operator<<*/ + } /*namespace detail*/ + } /*namespace tree*/ +} /*namespace xo*/ + +/* end IteratorUtil.hpp */ diff --git a/include/xo/tree/bplustree/LeafNode.hpp b/include/xo/tree/bplustree/LeafNode.hpp new file mode 100644 index 00000000..419bd63f --- /dev/null +++ b/include/xo/tree/bplustree/LeafNode.hpp @@ -0,0 +1,684 @@ +/* @file LeafNode.hpp */ + +#pragma once + +#include "GenericNode.hpp" +#include "indentlog/scope.hpp" +#include + +namespace xo { + namespace tree { + + // ----- LeafNodeItem ----- + + template + using LeafNodeItem = NodeItem; + + /* - define for symmetry with NodeItem + * - LeafNodeItem doesn't contain a child pointer; + * it belongs inside a leaf mode, which by definition doesn't have children + */ + template + struct NodeItem { + public: + using ContentsType = std::pair; + + public: + NodeItem() = default; + NodeItem(std::pair kv) : kv_pair_{std::move(kv)} {} + + std::pair const & kv_pair() const { return kv_pair_; } + + Key const & key () const { return kv_pair_.first; } + Value const & value() const { return kv_pair_.second; } + + void assign_value(Value x) { kv_pair_.second = std::move(x); } + + private: + /* key+value pair */ + std::pair kv_pair_; + }; /*NodeItem*/ + + /* struct with same size as LeafNodeItem, but POD + with no ctor/dtor */ + template + using LeafNodeItemPlaceholder = NodeItemPlaceholder; + + template + struct LeafNodeShim : public GenericNode + { + LeafNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode(ntype, branching_factor) {} + + /* ordinal_enabled: LeafNode will provide .size(): inherits+overrides GenericNodeBase.size() */ + }; + + template + struct LeafNodeShim : public GenericNode + { + LeafNodeShim(NodeType ntype, std::size_t branching_factor) : GenericNode(ntype, branching_factor) {} + + /* ordinal_disabled: LeafNode provides LeafNode::size(), but not used */ + + virtual std::size_t size() const = 0; + }; + + // ----- LeafNode ----- + + /* require: + * - Properties.branching_factor() + */ + template + struct LeafNode : public LeafNodeShim { + public: + using GenericNodeType = GenericNode; + using LeafNodeType = LeafNode; + using LeafNodeItemType = LeafNodeItem; + using LeafNodeItemPlaceholderType = LeafNodeItemPlaceholder; + using InternalNodeType = InternalNode; + + using ContentsType = typename LeafNodeItemType::ContentsType; + + public: + virtual ~LeafNode(); + + /* node size in bytes (increases with branching factor) */ + static std::size_t node_sizeof(std::size_t branching_factor); + + /* named ctor idiom. enforce heap allocation + unique_ptr wrapper */ + static std::unique_ptr make(std::pair kv_pair, + Properties const & properties); + + /* create+return new leaf node that contains all the items in *src from position [lo_ix, hi_ix), + * after this operation size of *src is reduced by (hi_ix - lo_ix) + */ + static std::unique_ptr annex(std::size_t lo_ix, + std::size_t hi_ix, + LeafNode * src); + + LeafNode * prev_leafnode() const { return prev_leafnode_; } + LeafNode * next_leafnode() const { return next_leafnode_; } + + /* .first: true if key in tree already + * .second: index position of (strict) least upper bound in .elt_v[] + * if .n_elt, key has no upper bound in this node + */ + std::pair find_lub_ix(Key const & key) const; + + LeafNodeItemType & lookup_elt(std::size_t i) { return *(reinterpret_cast(&(this->elt_v_[i]))); } + + LeafNodeItemType const & lookup_elt(std::size_t i) const { return *(reinterpret_cast(&(this->elt_v_[i]))); } + + void assign_leaf_value(std::size_t elt_ix, Value value) { + assert(elt_ix < this->n_elt_); + + this->lookup_elt(elt_ix).assign_value(std::move(value)); + } /*assign_leaf_value*/ + + /* assign precdeing leaf node (= LH sibling if share same parent) */ + void assign_prev_leafnode(LeafNode * x) { prev_leafnode_ = x; } + void assign_next_leafnode(LeafNode * x) { next_leafnode_ = x; } + + /* insert new leaf at position ix, associating key -> value + * (shuffle existing elements at ix, ix+1.. 1 position to the right) + */ + void insert_leaf_item(std::size_t ix, + std::pair const & kv_pair, + bool debug_flag); + + /* remove key,value pair at position ix */ + void remove_leaf(std::size_t ix, bool debug_flag); + + /* append n items from left-hand sibling, as new left-most elements + * require: combined #of items must be at most b = branching factor + */ + void prepend_from_lh_sibling(LeafNode * lh, std::size_t n, bool debug_flag); + + /* apepnd n items from right-hand sibling, as new right-most elements + * require: combined #of items must be at most b = branching factor + */ + void append_from_rh_sibling(std::size_t n, LeafNode * rh); + + void append_rh_sibling(LeafNode * rh) { this->append_from_rh_sibling(rh->n_elt(), rh); } + + /* returns new leaf with lower half of original element vector; + * original updated to retain upper half + */ + std::unique_ptr split_leaf_lower(); + + /* returns new leaf with upper half of original element vector; + * original updated to retain lower half + */ + std::unique_ptr split_leaf_upper(); + + /* memory for LeafNode instances is always created using new[], + * so required to use delete[] to deallocate + */ + void operator delete (void * mem) noexcept { ::operator delete[](mem); } + + // ----- Inherited from GenericNode ----- + + virtual std::size_t size() const override { return this->n_elt(); } + + virtual Key const & glb_key() const override { return this->lookup_elt(0).key(); } + + virtual std::size_t verify_helper(InternalNodeType const * parent, + bool with_lub_flag, + Key const & lub_key, + LeafNodeType const * lh_leaf, + LeafNodeType const * rh_leaf) const override; + virtual void verify_glb_key(Key const & key) const override; + virtual FindNodeResult find_min_leaf_node() override; + virtual FindNodeResult find_max_leaf_node() override; + + virtual void notify_remove() override; + + private: + explicit LeafNode(std::size_t branching_factor); + + LeafNode(std::pair const & kv_pair, + std::size_t branching_factor); + + void assign_siblings(LeafNode * prev, LeafNode * next); + + private: + /* previous LeafNode in key order, immediately before (all the keys in) this node. + * use to streamline inorder traversal. + */ + LeafNode * prev_leafnode_ = nullptr; + /* next LeafNode in key order, immediately after (all the keys in) this node. + * streamline inorder traversal. + */ + LeafNode * next_leafnode_ = nullptr; + /* flexible array; actual capacity will be Properties.branching_factor(); + * but only members [0 .. n_elt-1] are defined. + * + * actual type of .elt_v[i] is LeafNodeItem; + * need to use POD LeafNodeItemPlaceholder to satisfy flexible-array rules + */ + LeafNodeItemPlaceholderType elt_v_[]; + }; /*LeafNode*/ + + template + LeafNode::~LeafNode() { + /* since we're using flexible array for .elt_v[], need to manually run destructors */ + for (std::size_t i=0, n=this->branching_factor_; ilookup_elt(i).~LeafNodeItemType(); + } + + /* hygiene */ + this->n_elt_ = 0; + this->branching_factor_ = 0; + } /*dtor*/ + + template + std::size_t + LeafNode::node_sizeof(std::size_t branching_factor) { + /* since we're using flexible array for .elt_v[], need to manually account for it's allocated size */ + + return (sizeof(LeafNode) + + (branching_factor + * sizeof(LeafNodeItem))); + } /*node_sizeof*/ + + template + std::unique_ptr> + LeafNode::make(std::pair kv_pair, + Properties const & properties) + { + using xo::scope; + using xo::xtag; + + std::size_t mem_z = node_sizeof(properties.branching_factor()); + /* storage for LeafNode, including storage cost for flexible array LeafNode.elt_v[] */ + std::uint8_t * mem = new std::uint8_t[mem_z]; + +#ifdef NOT_IN_USE + scope x("LeafNode.make"); + x.log(xtag("sizeof(LeafNode)", sizeof(LeafNode)), + xtag("bf", properties.branching_factor()), + xtag("mem_z", mem_z), + xtag("mem", (void *)mem)); +#endif + + return std::unique_ptr(new (mem) LeafNode(std::move(kv_pair), + properties.branching_factor())); + } /*make*/ + + template + std::unique_ptr> + LeafNode::annex(std::size_t lo_ix, + std::size_t hi_ix, + LeafNode * src) + { + using xo::scope; + using xo::xtag; + + std::size_t branching_factor = src->branching_factor(); + + assert(hi_ix >= lo_ix); + assert(hi_ix - lo_ix <= branching_factor); + + std::size_t mem_z = node_sizeof(branching_factor); + std::uint8_t * mem = new std::uint8_t[mem_z]; + +#ifdef NOT_IN_USE + scope x("LeafNode.annex"); + x.log(xtag("sizeof(LeafNode)", sizeof(LeafNode)), + xtag("bf", branching_factor), + xtag("mem_z", mem_z), + xtag("mem", (void *)mem)); +#endif + + std::unique_ptr new_node(new (mem) LeafNode(branching_factor)); + + std::size_t old_n = src->n_elt(); + + new_node->n_elt_ = hi_ix - lo_ix; + + std::size_t n_annex = hi_ix - lo_ix; + + /* annexing from *src into new_node */ + for (std::size_t i = 0; i < n_annex; ++i) { + LeafNodeItemType & new_slot = new_node->lookup_elt(i); + + new_slot = std::move(src->lookup_elt(lo_ix + i)); + } + + /* shuffle over any remaining items in *src starting from hi_ix */ + for (std::size_t i = lo_ix; i + n_annex < old_n; ++i) { + LeafNodeItemType & slot = src->lookup_elt(i); + + slot = std::move(src->lookup_elt(i + n_annex)); + } + + src->n_elt_ = old_n - n_annex; + + if (lo_ix == 0) { + /* new node builds by taking leftmost elements from src + * -> new node becomes src's predecessor + */ + new_node->assign_siblings(src->prev_leafnode(), src); + } else { + /* new node builds by taking rightmost elements from src + * -> new node becomes src's successor + */ + new_node->assign_siblings(src, src->next_leafnode()); + } + + return new_node; + } /*annex*/ + + template + std::pair + LeafNode::find_lub_ix(Key const & key) const { + if (key < this->lookup_elt(0).key()) + return std::make_pair(false, 0); + + /* promise: return value >= 0 */ + + /* .elt_v[0 .. n_elt-1] are maintained in sorted key order */ + std::size_t lo = 0; + std::size_t hi = this->n_elt_; + + while (lo + 1 < hi) { + /* desired child item will be in range [lo, hi) */ + + std::size_t mid = lo + (hi - lo) / 2; + + if (key < this->lookup_elt(mid).key()) + hi = mid; + else + lo = mid; + } + + /* invariant: + * - lo is a valid index: elt_v[lo].kv_pair reflects outcome of most recent call to BplusTree.insert() + * - .elt_v[lo].key <= key + * - if hi<.n_elt, then key < .elt_v[hi].key + */ + bool presence_flag = (key == this->lookup_elt(lo).key()); + + return std::make_pair(presence_flag, hi); + } /*find_lub_ix*/ + + template + void + LeafNode::insert_leaf_item(std::size_t ix, + std::pair const & kv_pair, + bool debug_flag) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag), + xtag("self", this), + xtag("n_elt", this->n_elt()), + xtag("bf", this->branching_factor()), + xtag("ix", ix), + xtag("key", kv_pair.first), + xtag("value", kv_pair.second)); + + if (this->n_elt_ >= this->branching_factor()) { + assert(false); + throw std::runtime_error(tostr("LeafNode::insert_leaf: leaf already full", + xtag("leaf.n_elt", this->n_elt()), + xtag("branching_factor", this->branching_factor()))); + } + + std::size_t pos_ix = this->n_elt_; + + while (pos_ix > ix) { + //scope x1("loop"); + //x1.log(xtag("pos_ix", pos_ix)); + + this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix - 1)); + --pos_ix; + } + + ++(this->n_elt_); + this->lookup_elt(ix) = LeafNodeItemType(kv_pair); + + log.end_scope(); + } /*insert_leaf*/ + + template + void + LeafNode::remove_leaf(std::size_t ix, bool debug_flag) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag), + xtag("self", this), + xtag("n_elt", this->n_elt()), + xtag("bf", this->branching_factor()), + xtag("ix", ix)); + + if (this->n_elt_ == 0) { + throw std::runtime_error(tostr("LeafNode::remove_leaf: leaf already empty", + xtag("leaf.n_elt", this->n_elt()), + xtag("branching_factor", this->branching_factor()))); + } + + /* TODO: removal action for position pos_ix (maintain reductions) */ + + std::size_t pos_ix = ix; + std::size_t end_ix = this->n_elt_ - 1; + + while (pos_ix < end_ix) { + //scope x1("loop"); + //x1.log(xtag("pos_ix", pos_ix)); + + this->lookup_elt(pos_ix) = std::move(this->lookup_elt(pos_ix + 1)); + ++pos_ix; + } + + --(this->n_elt_); + } /*remove_leaf*/ + + template + void + LeafNode::prepend_from_lh_sibling(LeafNode * lh, std::size_t n, bool debug_flag) { + using xo::scope; + using xo::xtag; + + scope log(XO_DEBUG(debug_flag), + xtag("n", n)); + + if (this->n_elt() + n > this->branching_factor()) { + assert(false); + throw std::runtime_error(tostr("LeafNode.prepend_from_lh_sibling: expected combined #elt <= bf", + xtag("self.n_elt", this->n_elt()), + xtag("n", n), + xtag("bf", this->branching_factor()))); + } + + std::size_t n_lh = lh->n_elt(); + std::size_t n_rh = this->n_elt(); + + /* move elts in *this to the right n steps */ + for (std::size_t ixp1 = this->n_elt(); ixp1 > 0; --ixp1) { + std::size_t ix = ixp1 - 1; + this->lookup_elt(ix + n) = std::move(this->lookup_elt(ix)); + } + + /* xfer n elts from upper end of lh, to lower end of *this */ + for (std::size_t ix = 0; ix < n; ++ix) { + this->lookup_elt(ix) = lh->lookup_elt(n_lh - n + ix); + } + + this->n_elt_ += n; + lh->n_elt_ -= n; + + /* note: since we didn't create/destroy any LeafNodes, + * .prev_leafnode / .next_leafnode pointers are unchanged + */ + + log.end_scope(); + } /*prepend_from_lh_sibling*/ + + template + void + LeafNode::append_from_rh_sibling(std::size_t n, LeafNode * rh) { + using xo::xtag; + + if (this->n_elt() + n > this->branching_factor()) { + assert(false); + throw std::runtime_error(tostr("LeafNode.append_from_rh_sibling: expected combined #elt <= bf", + xtag("self.n_elt", this->n_elt()), + xtag("n", n), + xtag("bf", this->branching_factor()))); + } + + std::size_t n_lh = this->n_elt(); + + for (std::size_t ix = 0; ix < n; ++ix) { + this->lookup_elt(n_lh + ix) = std::move(rh->lookup_elt(ix)); + /* note: leaf items are key,value pairs; + * no parent pointers to fixup (cf InternalNode.append_from_rh_sibling) + */ + } + + this->n_elt_ += n; + + /* shuffle remaining members of rh sibling n items to the left */ + for (std::size_t ix = 0; ix < rh->n_elt() - n; ++ix) { + rh->lookup_elt(ix) = std::move(rh->lookup_elt(ix + n)); + } + + rh->n_elt_ -= n; + + /* note: since we didn't create/destroy any LeafNodes, + * .prev_leafnode / .next_leafnode pointers are unchanged + */ + + } /*append_from_rh_sibling*/ + + template + std::unique_ptr> + LeafNode::split_leaf_lower() { + std::size_t n_elt = this->n_elt_; + std::size_t mid_ix = n_elt / 2; + + return LeafNode::annex(0, mid_ix, this); + } /*split_leaf_lower*/ + + template + std::unique_ptr> + LeafNode::split_leaf_upper() { + std::size_t n_elt = this->n_elt_; + std::size_t mid_ix = n_elt / 2; + + return LeafNode::annex(mid_ix, n_elt, this); + } /*split_leaf_upper*/ + + template + std::size_t + LeafNode::verify_helper(InternalNodeType const * parent, + bool with_lub_flag, + Key const & lub_key, + LeafNodeType const * lh_leaf, + LeafNodeType const * rh_leaf) const { + using xo::xtag; + + /* verify immediate parent pointer is correct */ + if (this->parent() != parent) { + throw std::runtime_error(tostr("LeafNode::verify_helper" + ": expected parent pointer to refer to actual parent", + xtag("stored_parent", this->parent()), + xtag("actual_parent", parent))); + } + + /* verify locally stored keys appear in sorted order */ + std::size_t n = this->n_elt_; + for (std::size_t i=1; i < n; ++i) { + LeafNodeItemType const & prev = this->lookup_elt(i-1); + LeafNodeItemType const & elt = this->lookup_elt(i); + + if (prev.key() < elt.key()) { + ; + } else { + throw std::runtime_error(tostr("LeafNode::verify_helper" + ": expected local keys in strictly increasing order", + xtag("i", i), + xtag("key(i-1)", prev.key()), + xtag("key(i)", elt.key()))); + } + } + + if (with_lub_flag) { + if (this->lookup_elt(n-1).key() < lub_key) { + ; + } else { + throw std::runtime_error(tostr("LeafNode::verify_helper" + ": expected last local key before parent-supplied lub key", + xtag("n", n), + xtag("key(n-1)", this->lookup_elt(n-1).key()), + xtag("lub_key", lub_key))); + } + } + + /* verify next/prev leafnode pointers are consistent */ + if ((lh_leaf && (lh_leaf->next_leafnode() != this)) + || (this->prev_leafnode() != lh_leaf)) + { + throw std::runtime_error(tostr("LeafNode::verify_helper" + ": inconsistent prev/next leaf pointers", + xtag("parent", parent), + xtag("lh_leaf", lh_leaf), + xtag("lh_leaf.next", lh_leaf ? lh_leaf->next_leafnode() : nullptr), + xtag("self", this), + xtag("self.prev", this->prev_leafnode()))); + } + + if ((this->next_leafnode() != rh_leaf) + || (rh_leaf && (rh_leaf->prev_leafnode() != this))) + { + throw std::runtime_error(tostr("LeafNode::verify_helper" + ": inconsistent prev/next leaf pointers", + xtag("parent", parent), + xtag("self", this), + xtag("self.next", this->next_leafnode()), + xtag("rh_leaf", rh_leaf), + xtag("rh_leaf.prev", rh_leaf ? rh_leaf->prev_leafnode() : nullptr))); + } + + return this->n_elt(); + } /*verify_helper*/ + + template + void + LeafNode::verify_glb_key(Key const & key) const { + using xo::xtag; + + LeafNodeItemType const & elt = this->lookup_elt(0); + + if (elt.key() != key) { + throw std::runtime_error(tostr("LeafNode::verify_glb_key" + ": expected stored greatest-lower-bound key to match leftmost leaf's key", + xtag("@", this), + xtag("reported_key", key), + xtag("actual_key", elt.key()))); + } + } /*verify_glb_key*/ + + template + FindNodeResult> + LeafNode::find_min_leaf_node() { + return FindNodeResult>(0, this); + } /*find_min_leaf_node*/ + + template + FindNodeResult> + LeafNode::find_max_leaf_node() { + return FindNodeResult>(0, this); + } /*c_find_max_leaf_node*/ + + template + void + LeafNode::notify_remove() { + if (this->prev_leafnode_) + this->prev_leafnode_->assign_next_leafnode(this->next_leafnode_); + if (this->next_leafnode_) + this->next_leafnode_->assign_prev_leafnode(this->prev_leafnode_); + } /*notify_remove*/ + + template + LeafNode::LeafNode(std::size_t branching_factor) + : LeafNodeShim(NodeType::leaf, branching_factor) + { + /* must call ctor explicitly for each element. + * compiler can't do this for us, b/c it doesn't know size of flexible array + */ + for (std::size_t i = 0, n = branching_factor; i < n; ++i) { + new (&(this->lookup_elt(i))) LeafNodeItemType(); + } + } + + template + LeafNode::LeafNode(std::pair const & kv_pair, + std::size_t branching_factor) + : LeafNodeShim(NodeType::leaf, branching_factor) + { + using xo::scope; + using xo::xtag; + +#ifdef NOT_USING_DEBUG + scope x("LeafNode.ctor"); +#endif + + this->n_elt_ = 1; + /* since .elt_v[] is a flexible array, need to invoke constructors explicitly + * (compiler doesn't know how many elements there are -> can't do it for us + */ + +#ifdef NOT_USING_DEBUG + x.log(xtag("elt[0]", &(this->lookup_elt(0)))); +#endif + + new (&(this->lookup_elt(0))) LeafNodeItemType(kv_pair); + + for (std::size_t i = 1, n = branching_factor; i < n; ++i) { +#ifdef NOT_USING_DEBUG + x.log(xtag("i", i), + xtag("elt[i]", &(this->lookup_elt(i)))); +#endif + + /* using placement-new to invoke ctor explicitly */ + new (&(this->lookup_elt(i))) LeafNodeItemType(); + } + } /*ctor*/ + + template + void + LeafNode::assign_siblings(LeafNode * p, LeafNode * n) { + if (p) + p->assign_next_leafnode(this); + this->prev_leafnode_ = p; + this->next_leafnode_ = n; + if (n) + n->assign_prev_leafnode(this); + } /*assign_siblings*/ + + } /*namespace tree*/ +} /*namespace xo*/ + + +/* end LeafNode.hpp */ diff --git a/include/xo/tree/bplustree/Lhs.hpp b/include/xo/tree/bplustree/Lhs.hpp new file mode 100644 index 00000000..3968a76d --- /dev/null +++ b/include/xo/tree/bplustree/Lhs.hpp @@ -0,0 +1,68 @@ +/* @file Lhs.hpp */ + +#pragma once + +#include + +namespace xo { + namespace tree { + namespace detail { + /* xo::tree::detail::BplusTreeLhsBase + * + * use for {const + non-const} versions of BplusTree::operator[] + * + * Expect: either: + * Tree = BplusTree + * LeafNodeItem = Tree::LeafNodeItemType + * or + * Tree = BplusTree const + * LeafNodeItem = Tree::LeafNodeItemType const + */ + template + class BplusTreeLhsBase { + public: + using mapped_type = typename Tree::mapped_type; + + public: + BplusTreeLhsBase() = default; + BplusTreeLhsBase(Tree * tree, LeafNodeItem const * item) + : p_tree_{tree}, item_{item} {} + + operator mapped_type const & () const { + //using xo::tostr; + + if (!this->item_) { + throw std::runtime_error + ("bptree: attempt to use empty lhs object as rvalue"); + } + + return this->item_->value(); + } + + protected: + Tree * p_tree_ = nullptr; + /* points to key-value pair (interior to a B+ tree LeafNode */ + LeafNodeItem * item_ = nullptr; + }; /*BplusTreeLhsBase*/ + + /* xo::tree::detail::BplusTreeConstLhs + * + * use for const version of BplusTree::operator[] + */ + template + class BplusTreeConstLhs : public BplusTreeLhsBase + { + public: + BplusTreeConstLhs() = default; + BplusTreeConstLhs(BplusTree const * tree, + typename BplusTree::LeafNodeItemType const * item) + : BplusTreeLhsBase(tree, item) {} + }; /*BplusTreeConstLhs*/ + + } /*namespace detail*/ + } /*namespace tree*/ +} /*namespace xo*/ + +/* end Lhs.hpp */ diff --git a/include/xo/tree/bplustree/bplustree_tags.hpp b/include/xo/tree/bplustree/bplustree_tags.hpp new file mode 100644 index 00000000..1f589bee --- /dev/null +++ b/include/xo/tree/bplustree/bplustree_tags.hpp @@ -0,0 +1,16 @@ +/* @file bplustree_tags.hpp */ + +#pragma once + +namespace xo { + namespace tree { + namespace tags { + /* ordinal_enabled: compute ordinal statistics; + * in particular maintain per-node subtree size + */ + enum ordinal_tag { ordinal_enabled, ordinal_disabled }; + } /*tags*/ + } /*namespace tree*/ +} /*namespace xo*/ + +/* end bplustree_tags.hpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..ea866722 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,47 @@ +# tree/utest/CMakeLists.txt + +# note: tests in this directory use Catch2-provided main +set(SELF_EXE utest.tree) +set(SELF_SOURCE_FILES tree_utest_main.cpp redblacktree.cpp bplustree.cpp) + +add_executable(${SELF_EXE} ${SELF_SOURCE_FILES}) +xo_include_options2(${SELF_EXE}) + +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +target_code_coverage(${SELF_EXE} AUTO ALL) + +# ---------------------------------------------------------------- +# internal dependencies: refcnt, ... + +xo_internal_dependency(${SELF_EXE} refcnt) +xo_internal_dependency(${SELF_EXE} indentlog) +xo_dependency_headeronly(${SELF_EXE} randomgen) + +# ---------------------------------------------------------------- +# 3rd part dependency: catch2: + +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# 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 +# + +# let's see if xo_external_target_dependency() works for these.. +#find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") +#target_include_directories(${SELF_UTEST_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 tree/utest/CMakeLists.txt diff --git a/utest/bplustree.cpp b/utest/bplustree.cpp new file mode 100644 index 00000000..5dac55a8 --- /dev/null +++ b/utest/bplustree.cpp @@ -0,0 +1,813 @@ +/* @file bplustree.cpp */ + +#define CATCH_CONFIG_ENABLE_BENCHMARKING + +#include "random_tree_ops.hpp" +#include "xo/tree/BplusTree.hpp" +#include "randomgen/random_seed.hpp" +#include "indentlog/scope.hpp" +#include "catch2/catch.hpp" + +namespace { + using xo::tree::BplusTree; + using xo::tree::BplusStdProperties; + using xo::tree::NullReduce; + using xo::tree::Machdep; + + using xo::rng::Seed; + + using utest::TreeUtil; + + using xo::scope; + using xo::scope_setup; + using xo::xtag; + + using BtreeKey = int; + using BtreeValue = double; + using BtreeProperties = BplusStdProperties; + //using BtreeProperties = BplusStdProperties; + using BpTree = BplusTree, + BtreeProperties>; + + /* random test data (e.g. permutation of integers [0 .. n-1]). + * will do various tree operations using these permutations to control order + * in which keys are presented + */ + struct RandomTestData { + RandomTestData(std::size_t n, + xo::rng::xoshiro256ss * p_rgen); + + std::vector const & u1v() const { return u1v_; } + std::vector const & u2v() const { return u2v_; } + std::vector const & u12_v() const { return u12_v_; } + + private: + /* a set comprising n randomly chosen elements drawn from [0 .. 2n-1]. + * here n = .u1v.size = .u2v.size + */ + std::vector u1v_; + /* complement of .u1v w.r.t. [0 .. 2n-1] */ + std::vector u2v_; + /* .u1v + .u2v */ + std::vector u12_v_; + }; /*RandomTestData*/ + + RandomTestData::RandomTestData(std::size_t n, + xo::rng::xoshiro256ss * p_rgen) + : u1v_(n), u2v_(n), u12_v_(2*n) + { + /* permutation of [0 .. 2n-1] */ + std::vector u(2*n); + + for (std::uint32_t i=0; i<2*n; ++i) + u[i] = i; + std::shuffle(u.begin(), u.end(), *p_rgen); + + u1v_ = std::vector(u.begin(), u.begin() + n); + u2v_ = std::vector(u.begin() + n, u.end()); + u12_v_ = std::move(u); + } /*ctor*/ + + /* representation-independent feature benchmarks for tree algorithms. + * + * +------------------+ + * |AbstractTestParams| + * +------------------+ + * ^ + * | isa +----------------+ + * +------------|StdMapTestParams| benchmark std::map (bogey!) + * | +----------------+ + * | + * | isa +---------------+ + * +------------|BtreeTestParams| benchmark BplusTree + * +---------------+ + */ + struct AbstractTestParams { + virtual ~AbstractTestParams() = default; + /* insert benchmark: + * 1. prime tree by inserting RandomTestData.u1v (random subset comprising n draws from [0 .. 2n-1]) + * 2. measure cost of inserting RandomTestData.u2v (complement of u1v w.r.t [0 .. 2n-1]) + */ + virtual void run_insert_benchmark(RandomTestData const & random_testdata) const = 0; + virtual void run_erase_benchmark(RandomTestData const & random_testdata) const = 0; + virtual void run_lookup_benchmark(RandomTestData const & random_testdata) const = 0; + virtual void run_traverse_benchmark(RandomTestData const & random_testdata) const = 0; + }; + + struct StdMapTestParams : public AbstractTestParams { + StdMapTestParams(char const * name) + : test_name_{name} {} + + /* 1. make map containing keys in random_testdata.u1v. + * 2. during construction, interleave inserts against a temporary map, + * to spoil sequential heap allocation (i.e. simulate fragmentation) + */ + std::map make_random_map1(RandomTestData const & random_testdata) const { + std::map tree; + /* 2nd tree to interfere with locality */ + std::map tree2; + + for (std::uint32_t x : random_testdata.u1v()) { + tree.insert({x, 10*x}); + /* 2nd tree to interfere with locality */ + for (std::uint32_t y = 0; y < 8; ++y) + tree2.insert({8*x+y, 10*8*x+y}); + } + + return tree; + } /*make_random_map1*/ + + /* 1. make map containing keys in both random_testdata.u1v + random_testdata.u2v + * 2. during construction, interleave inserts against a temporary map, + * to spoil sequential heap allocation (i.e. simulate fragmentation) + */ + std::map make_random_map12(RandomTestData const & random_testdata) const { + std::map tree; + /* temporary tree to interfere with locality */ + std::map tree2; + + for (std::uint32_t x : random_testdata.u12_v()) { + tree.insert({x, 10*x}); + /* 2nd tree to interfere with memory locality */ + for (std::uint32_t y = 0; y < 8; ++y) + tree2.insert({8*x+y, 10*8*x+y}); + } + + return tree; + } /*make_random_map12*/ + + virtual void run_insert_benchmark(RandomTestData const & random_testdata) const override; + virtual void run_erase_benchmark(RandomTestData const & random_testdata) const override; + virtual void run_lookup_benchmark(RandomTestData const & random_testdata) const override; + virtual void run_traverse_benchmark(RandomTestData const & random_testdata) const override; + + char const * test_name_ = nullptr; + }; + + void + StdMapTestParams::run_insert_benchmark(RandomTestData const & random_testdata) const + { + /* see also: BtreeTestParams::run_insert_benchmark() */ + + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + std::map tree + = std::move(this->make_random_map1(random_testdata)); + + /* benchmark additional inserts */ + clock.measure([&](int seq) { + std::size_t key = random_testdata.u2v()[seq % n]; + double value = 10 * key; + + tree.insert({key, value}); + return tree.size(); + }); + }; + } /*run_insert_benchmark*/ + + void + StdMapTestParams::run_erase_benchmark(RandomTestData const & random_testdata) const + { + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + std::map tree + = std::move(this->make_random_map12(random_testdata));; + + clock.measure([&](int seq) { + /* catch2 decides how many times to run this lambda, + * in effort to get statistically valid sample. + * + * If it calls lambda n times, then seq will increase from [0 .. n-1] + */ + + std::size_t key = random_testdata.u1v()[seq % n]; + + //std::clog << "i=" << i << std::endl; + tree.erase(key); + + return tree.size(); + }); + }; + } /*run_erase_benchmark*/ + + void + StdMapTestParams::run_lookup_benchmark(RandomTestData const & random_testdata) const + { + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + std::map tree + = std::move(this->make_random_map1(random_testdata)); + + clock.measure([&](int seq) { + /* catch2 decides how many times to run this lambda, + * in effort to get statistically valid sample. + * + * If it calls lambda n times, then seq will increase from [0 .. n-1] + */ + + std::size_t key = random_testdata.u1v()[seq % n]; + + //std::clog << "i=" << i << std::endl; + double value = tree[key]; + + return value; + }); + }; + } /*run_lookup_benchmark*/ + + void + StdMapTestParams::run_traverse_benchmark(RandomTestData const & random_testdata) const + { + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + std::map tree + = std::move(this->make_random_map1(random_testdata)); + + clock.measure([&](int seq) { + /* catch2 decides how many times to run this lambda, + * in effort to get statistically valid sample. + * + * If it calls lambda n times, then seq will increase from [0 .. n-1] + */ + + std::size_t key = random_testdata.u1v()[seq % n]; + + //std::clog << "i=" << i << std::endl; + double value = tree[key]; + + return value; + }); + }; + } /*run_traverse_benchmark*/ + + struct BtreeTestParams : public AbstractTestParams { + BtreeTestParams(char const * name, std::size_t bf, bool debug_flag) + : test_name_{name}, branching_factor_{bf}, debug_flag_{debug_flag} {} + + BpTree make_empty_bptree() const { + BtreeProperties properties(branching_factor_, + debug_flag_); + return BpTree(properties); + } + + /* 1. make b+ tree containing keys in random_testdata.u1v. + * 2. during constructions, interleave inserts against a temporary b+ tree, + * to spoil sequential heap allocation (i.e. simulate fragmentation) + */ + BpTree make_random_bptree1(RandomTestData const & random_testdata) const { + BpTree bptree = this->make_empty_bptree(); + /* 2nd tree, just to spoil memory locality */ + BpTree bptree2 = this->make_empty_bptree(); + + for (std::uint32_t x : random_testdata.u1v()) { + bptree.insert(BpTree::value_type(x, 10 * x)); + /* 2nd tree to interfere with locality */ + for (std::uint32_t y = 0; y < 8; ++y) { + bptree2.insert(BpTree::value_type(8*x+y, 10 * (8*x+y))); + } + } + + return bptree; + } /*make_random_bptree1*/ + + BpTree make_random_bptree12(RandomTestData const & random_testdata) const { + BpTree bptree = this->make_empty_bptree(); + /* 2nd tree, just to spoil memory locality */ + BpTree bptree2 = this->make_empty_bptree(); + + for (std::uint32_t x : random_testdata.u12_v()) { + bptree.insert(BpTree::value_type(x, 10 * x)); + /* 2nd tree to interfere with locality */ + for (std::uint32_t y = 0; y < 8; ++y) { + bptree2.insert(BpTree::value_type(8*x+y, 10 * (8*x+y))); + } + } + + return bptree; + } /*make_random_bptree12*/ + + void run_unit_test(xo::rng::xoshiro256ss * p_rgen) const; + + virtual void run_insert_benchmark(RandomTestData const & random_testdata) const override; + virtual void run_erase_benchmark(RandomTestData const & random_testdata) const override; + virtual void run_lookup_benchmark(RandomTestData const & random_testdata) const override; + virtual void run_traverse_benchmark(RandomTestData const & random_testdata) const override; + + /* test (or benchmark) name -- 1st argument to catch2 TEST_CASE() / BENCHMARK() / SECTION() macro */ + char const * test_name_ = nullptr; + /* exercise B+ tree with this branching factor */ + std::size_t branching_factor_ = 0; + /* for benchmarks only: if true enable verbose logging of B+ tree operations. otherwise not used */ + bool debug_flag_ = false; + }; /*BtreeTestParams*/ + + void + BtreeTestParams::run_unit_test(xo::rng::xoshiro256ss * p_rgen) const + { + std::size_t branching_factor = this->branching_factor_; + + /* perform a series of tests with increasing scale */ + for (std::uint32_t n = 0; n <= 1024;) { + if (n == 0) { + bool ok_flag = false; + + for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) { + ok_flag = true; + + bool debug_flag = (attention == 1); + + BtreeProperties properties(branching_factor, + debug_flag); + BpTree bptree(properties); + + scope log(XO_DEBUG2(debug_flag, "bptree"), + xtag("vm_page_size", Machdep::get_page_size()), + xtag("branching_factor", bptree.branching_factor()), + xtag("leaf_node_size", sizeof(BpTree::LeafNodeType)), + xtag("internal_node_size", sizeof(BpTree::InternalNodeType))); + + REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.size() == 0); + REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.verify_ok(true) == true); + + log && log(xtag("size", n)); + + ok_flag &= TreeUtil::check_bidirectional_iterator(0 /*dvalue - not used*/, + debug_flag, + bptree); + + ok_flag &= TreeUtil::test_clear(debug_flag, &bptree); + + log.end_scope(); + } + } else { + /* for each tree size, do multiple trials; + * choosing different pseudorandom key order for each trial + */ + for (std::uint32_t trial = 0; trial < 10; ++trial) { + /* repeated trials with different rng state */ + + bool ok_flag = false; + + for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) { + ok_flag = true; + + /* attention=0: + * - no logging + * - detect assertion failures, but don't report them to catch + * attention=1: + * - only runs if failure detected with attention=0 + * - full logging + * - report to catch + */ + + bool debug_flag = (attention == 1); + + BtreeProperties properties(branching_factor, + debug_flag); + BpTree bptree(properties); + + scope log(XO_DEBUG2(debug_flag, "bptree"), + xtag("vm_page_size", Machdep::get_page_size()), + xtag("branching_factor", bptree.branching_factor()), + xtag("leaf_node_size", sizeof(BpTree::LeafNodeType)), + xtag("internal_node_size", sizeof(BpTree::InternalNodeType))); + + REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.size() == 0); + REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.verify_ok(true) == true); + + log && log(xtag("size", n), xtag("trial", trial)); + + /* insert [0..n-1] in random order */ + ok_flag &= TreeUtil::random_inserts(n, debug_flag, p_rgen, &bptree); + + /* verification problem -> print tree */ + log && log(xtag("bptree", (char const *)"...")); + if (log) bptree.print(std::cout, log.nesting_level() + 2); + + try { + REQUIRE_ORCAPTURE(ok_flag, debug_flag, bptree.verify_ok(debug_flag)); + } catch(std::exception & ex) { + log && log(xtag("exception", ex.what())); + } + + if (properties.ordinal_enabled()) { + ok_flag &= TreeUtil::check_ordinal_lookup(0 /*dvalue*/, + debug_flag, + bptree); + } + + /* verify inorder traverse, using iterator api */ + ok_flag &= TreeUtil::check_bidirectional_iterator(0, + debug_flag, + bptree); + + ok_flag &= TreeUtil::random_lookups(debug_flag, + bptree, + p_rgen); + + if (properties.ordinal_enabled()) { + /* paranoid check that iteration / random_lookups didn't somehow disturb tree */ + ok_flag &= TreeUtil::check_ordinal_lookup(0 /*dvalue*/, + debug_flag, + bptree); + } + + /* TODO: + * - check_reduced_sum() + * - check_ordinal_lookup() + * - check_bidirectional_iterator() + * - random_updates() + * - check_ordinal_lookup() + * - check_bidirectional_iterator() + * - check_reduced_sum() + */ + + /* remove [0..n-1] in random order */ + ok_flag &= TreeUtil::random_removes(debug_flag, p_rgen, &bptree); + + /* insert [0..n-1] again, so we can test .clear() */ + ok_flag &= TreeUtil::random_inserts(n, debug_flag, p_rgen, &bptree); + + ok_flag &= TreeUtil::test_clear(debug_flag, &bptree); + + log.end_scope(); + } /*loop over attention value*/ + } /*loop over trial#*/ + } + + if (n == 0) + n = 1; + else + n = 2*n; + } + } /*run_unit_test*/ + + void + BtreeTestParams::run_insert_benchmark(RandomTestData const & random_testdata) const + { + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + BpTree bptree = std::move(this->make_random_bptree1(random_testdata)); + + /* benchmark additional inserts (don't want to benchmark on empty tree) */ + clock.measure([&](int seq) { + /* catch2 decides how many times to run this lambda, + * in effort to get statistically valid sample. + * + * If it calls lambda n times, then seq will increase from [0 .. n-1] + */ + + std::size_t key = random_testdata.u2v()[seq % n]; + double value = 10 * key; + + bptree.insert(BpTree::value_type(key, value)); + + return bptree.size(); + }); + }; + } /*run_insert_benchmark*/ + + void + BtreeTestParams::run_erase_benchmark(RandomTestData const & random_testdata) const + { + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + /* b+ tree with 2n elements */ + BpTree bptree = std::move(this->make_random_bptree12(random_testdata)); + + /* measure time to remove n elements */ + clock.measure([&](int seq) { + /* catch2 decides how many times to run this lambda, + * in effort to get statistically valid sample. + * + * If it calls lambda n times, then seq will increase from [0 .. n-1] + */ + + //std::clog << "i=" << i << std::endl; + bptree.erase(random_testdata.u1v()[seq % n]); + + return bptree.size(); + }); + }; + } /*run_erase_benchmark*/ + + void + BtreeTestParams::run_lookup_benchmark(RandomTestData const & random_testdata) const + { + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + BpTree bptree = std::move(this->make_random_bptree1(random_testdata)); + + /* benchmark random lookups */ + clock.measure([&](int seq) { + /* catch2 decides how many times to run this lambda, + * in effort to get statistically valid sample. + * + * If it calls lambda n times, then seq will increase from [0 .. n-1] + */ + + std::size_t key = random_testdata.u1v()[seq % n]; + + double value = bptree[key]; + + return value; + }); + }; + } /*run_lookup_benchmark*/ + + void + BtreeTestParams::run_traverse_benchmark(RandomTestData const & random_testdata) const + { + BENCHMARK_ADVANCED(this->test_name_)(Catch::Benchmark::Chronometer clock) + { + std::size_t n = random_testdata.u1v().size(); + + BpTree bptree = std::move(this->make_random_bptree1(random_testdata)); + + /* benchmark traverse */ + BpTree::const_iterator ix = bptree.begin(); + + clock.measure([&](int seq) { + /* catch2 decides how many times to run this lambda, + * in effort to get statistically valid sample. + * + * If it calls lambda n times, then seq will increase from [0 .. n-1] + */ + + if (seq % n == 0) + ix = bptree.begin(); + + return ix++; + }); + }; + } /*run_traverse_benchmark*/ + + TEST_CASE("bptree", "[bplustree]") { + uint64_t seed = 14950349842636922572UL; + /* can reseed from /dev/random with: */ + //Seed seed; + + auto rgen = xo::rng::xoshiro256ss(seed); + + /* exercise multiple branching factors */ + std::array const params_v + = {{ + BtreeTestParams("bf=4", + 4 /*branching_factor*/, + false /*debug_flag - not used*/), + BtreeTestParams("bf=12", + 12 /*branching_factor*/, + false /*debug_flag - not used*/), + BtreeTestParams("bf=28", + 28 /*branching_factor*/, + false /*debug_flag - not used*/), + BtreeTestParams("bf=60", + 60 /*branching_factor*/, + false /*debug_flag - not used*/) + }}; + + for (std::uint32_t i_pm = 0; i_pm < params_v.size(); ++i_pm) { + SECTION(params_v[i_pm].test_name_) { + params_v[i_pm].run_unit_test(&rgen); + } + } + } /*TEST_CASE(bptree)*/ + + /* to run: + * $ ./utest.tree [!benchmark] + * + * looks like ospage4 (1k nodes) gets best performance + */ + TEST_CASE("bptree-benchmark", "[!benchmark]") { + using BtreeProperties = BplusStdProperties; + + /* 2 cache lines per node (though note that we're not aligning nodes on cacheline boundaries) */ + std::size_t const c_cacheline_branching_factor = 4; // BtreeProperties::default_cacheline_branching_factor(); + std::size_t const c_ospage16_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 16); + std::size_t const c_ospage8_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 8); + std::size_t const c_ospage4_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 4); + std::size_t const c_ospage2_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size() / 2); + std::size_t const c_ospage1_branching_factor = BtreeProperties::branching_factor_for_size(Machdep::get_page_size()); + + /* random seed -- we don't need deterministic behavior for benchmarking, unless we encounter internal logic error */ + //std::uint64_t seed = 17372468046414980217UL; + Seed seed; + + auto rgen = xo::rng::xoshiro256ss(seed); + + constexpr bool c_debug_flag = false; + + /* n keys [0 .. n-1] */ + std::uint32_t n = 25000; + + RandomTestData random_testdata(n, &rgen); + +#ifdef OBSOLETE + /* random permutation of [0..n-1] */ + std::vector u(n); + { + for (std::uint32_t i=0; i u2(n); + { + for (std::uint32_t i=0; i, 7> const params_v + = {{ + std::unique_ptr(new StdMapTestParams("std-map-insert")), + std::unique_ptr(new BtreeTestParams("bplus-min-insert", + c_cacheline_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage16-insert", + c_ospage16_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage8-insert", + c_ospage8_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage4-insert", + c_ospage4_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage2-insert", + c_ospage2_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage-insert", + c_ospage1_branching_factor, + false)) + }}; + + /* note: w/cacheline: + * getting 593ms for 10^6 inserts; + * i.e. ~593ns each + * w/ospage: + * getting 188ms for 10^6 inserts; + * i.e. ~188ns each + * (with ospage size 4k -> branching factor 252) + */ + for(std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) { + params_v[i_bm]->run_insert_benchmark(random_testdata); + } + } + + { + std::array, 7> const params_v + = {{ + std::unique_ptr(new StdMapTestParams("std-map-erase")), + std::unique_ptr(new BtreeTestParams("bplus-min-remove", + c_cacheline_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage16-remove", + c_ospage16_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage8-remove", + c_ospage8_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage4-remove", + c_ospage8_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage2-remove", + c_ospage8_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage1-remove", + c_ospage1_branching_factor, + false)) + }}; + + /* note: cacheline: getting 72us for 10^2 removes; + * i.e. ~7.2ns each + * + * ospage: getting 243us for 10^4 removes; + * i.e. ~24ns each + */ + for (std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) { + params_v[i_bm]->run_erase_benchmark(random_testdata); + } + } + + { + std::array, 7> const params_v + = {{ + std::unique_ptr(new StdMapTestParams("std-map-lookup")), + std::unique_ptr(new BtreeTestParams("bplus-min-lookup", + c_cacheline_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage16-lookup", + c_ospage16_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage8-lookup", + c_ospage8_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage4-lookup", + c_ospage4_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage2-lookup", + c_ospage2_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage1-lookup", + c_ospage1_branching_factor, + false)) + }}; + + /* note: cacheline: + * getting 850us for 10^4 lookups; + * -> ~85ns each + * ospage: + * getting 585us for 10^4 lookups; + * -> ~58ns each + */ + for (std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) { + params_v[i_bm]->run_lookup_benchmark(random_testdata); + } + } + + { + std::array, 7> const params_v + = {{ + std::unique_ptr(new StdMapTestParams("std-map-traverse")), + std::unique_ptr(new BtreeTestParams("bplus-min-traverse", + c_cacheline_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage16-traverse", + c_ospage16_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage8-traverse", + c_ospage8_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage4-traverse", + c_ospage4_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage2-traverse", + c_ospage2_branching_factor, + false)), + std::unique_ptr(new BtreeTestParams("bplus-ospage1-traverse", + c_ospage1_branching_factor, + false)) + }}; + + /* note: cacheline: getting 25us to traverse tree of size 10^4 + * -> ~2.5ns each + * note: ospage: getting 6us to traverse tree of size 10^4 + * -> ~0.6ns each + */ + for (std::uint32_t i_bm = 0; i_bm < params_v.size(); ++i_bm) { + params_v[i_bm]->run_traverse_benchmark(random_testdata); + } + } + + } /*TEST_CASE(bptree-benchmark)*/ +} /*namespace*/ + +/* end bplustree.cpp */ diff --git a/utest/random_tree_ops.hpp b/utest/random_tree_ops.hpp new file mode 100644 index 00000000..b81601f7 --- /dev/null +++ b/utest/random_tree_ops.hpp @@ -0,0 +1,450 @@ +/* @file random_tree_ops.hpp **/ + +#include "randomgen/xoshiro256.hpp" +#include "indentlog/scope.hpp" +#include "indentlog/print/tag.hpp" +#include "indentlog/print/vector.hpp" +#include "catch2/catch.hpp" +#include +#include +#include + +namespace utest { + struct Util { + /* generate vector with integers [0.. n-1] */ + static std::vector vector_upto(std::uint32_t n) { + std::vector u(n); + for (std::uint32_t i = 0; i < n; ++i) + u[i] = i; + + return u; + } /*vector_upto*/ + + static std::map + map_upto(std::uint32_t n) + { + std::map m; + for(std::uint32_t i=0; i + random_permutation(uint32_t n, xo::rng::xoshiro256ss *p_rgen) { + /* vector [0 .. n-1] */ + std::vector u = vector_upto(n); + + /* shuffle to get unpredictable permutation */ + std::shuffle(u.begin(), u.end(), *p_rgen); + + return u; + } /*random_permutation*/ + }; /*Util*/ + +/* note: trivial REQUIRE() call in else branch bc we still want + * catch2 to count assertions when verification succeeds + */ +# define REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr) \ + if (catch_flag) { \ + REQUIRE((expr)); \ + } else { \ + REQUIRE(true); \ + ok_flag &= (expr); \ + } + +# define REQUIRE_ORFAIL(ok_flag, catch_flag, expr) \ + REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr); \ + if (!ok_flag) \ + return ok_flag + + + template + struct TreeUtil : public Util { + static bool + test_clear(bool catch_flag, + Tree * p_tree) + { + bool ok_flag = true; + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok()); + + p_tree->clear(); + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag)); + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->empty()); + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == 0); + + return ok_flag; + } /*test_clear*/ + + /* do n random inserts (taken from *p_rgen) into *p_rbtreẹ + * inserted keys will be distinct values in [0, .., n-1] + */ + static bool + random_inserts(std::uint32_t n, + bool catch_flag, + xo::rng::xoshiro256ss * p_rgen, + Tree * p_tree) + { + using xo::xtag; + + bool ok_flag = true; + + xo::scope log(XO_DEBUG(catch_flag)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok()); + + /* n keys 0..n-1 */ + std::vector u(n); + for(std::uint32_t i=0; iinsert(typename Tree::value_type(x, 10 * x)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.second); + + /* verify: iterator returned by Treẹinsert(), refers to inserted key,value pair */ + log && log(xtag("iter.node", insert_result.first.node())); + REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->first == x); + REQUIRE_ORFAIL(ok_flag, catch_flag, insert_result.first->second == 10 * x); + + ++i; + } + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == n); + + return ok_flag; + } /*random_inserts*/ + + /* do n random removes (taken from *p_rgen) from *p_rbtree; + * assumes *p_rbtree has keys [0 .. n-1] where n=p_rbtreẹsize + */ + static bool + random_removes(bool catch_flag, + xo::rng::xoshiro256ss * p_rgen, + Tree * p_tree) + { + using xo::scope; + using xo::xtag; + + bool ok_flag = true; + + xo::scope log(XO_DEBUG(catch_flag)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag)); + + uint32_t n = p_tree->size(); + + /* random permutation of keys in *p_tree */ + std::vector u + = random_permutation(n, p_rgen); + + log && log(xtag("remove-order", u)); + + /* will keep track of which keys remain as we move them */ + std::map m = Util::map_upto(n); + + /* remove keys in permutation order */ + std::uint32_t i = 1; + for (std::uint32_t x : u) { + log && log("iter i: removing key from n-node tree", + xtag("i", i), xtag("key", x), xtag("n", n)); + + /* remove x from tracking map m also */ + m.erase(x); + + log && log("remove key :iter ", i, "/", n, xtag("key", x)); + + p_tree->erase(x); + // rbtreẹdisplay(); + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == n-i); + /* amongst other things, this guarantees that keys in *p_tree + * appear in increasing order + */ + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->verify_ok(catch_flag)); + +#ifdef NOT_YET + /* 1. rbtree should now contain all the keys in [0..n-1], + * with u[0]..u[i-1] excluded; this is the same as the + * contents of m. + */ + auto m_ix = m.begin(); + auto m_end_ix = m.end(); + auto visitor_fn = + ([&m_ix, m_end_ix] + (std::pair const & contents) + { + REQUIRE(m_ix != m_end_ix); + REQUIRE(contents.first == m_ix->second); + ++m_ix; + }); + p_tree->visit_inorder(visitor_fn); +#endif + ++i; + } + + REQUIRE_ORFAIL(ok_flag, catch_flag, m.empty()); + REQUIRE_ORFAIL(ok_flag, catch_flag, p_tree->size() == 0); + + log.end_scope(); + + return ok_flag; + } /*random_removes*/ + + /* Require: + * - tree has keys [0..n-1], where n=treẹsize() + * - for each key k, associated value is 10*k + */ + static bool + random_lookups(bool catch_flag, + Tree const & tree, + xo::rng::xoshiro256ss * p_rgen) + { + using xo::scope; + using xo::xtag; + + xo::scope log(XO_DEBUG(catch_flag)); + + /* -> false if/when verification fails */ + bool ok_flag = true; + + REQUIRE_ORFAIL(ok_flag, catch_flag, tree.verify_ok(catch_flag)); + + size_t n = tree.size(); + std::vector u + = random_permutation(n, p_rgen); + + /* lookup keys in permutation order */ + std::uint32_t i = 1; + for (std::uint32_t x : u) { + INFO(tostr(xtag("i", i), xtag("n", n), xtag("x", x))); + + REQUIRE_ORFAIL(ok_flag, catch_flag, tree[x] == x*10); + REQUIRE_ORFAIL(ok_flag, catch_flag, tree.verify_ok(catch_flag)); + REQUIRE_ORFAIL(ok_flag, catch_flag, tree.size() == n); + + /* also test treẹfind() */ + auto find_ix = tree.find(x); + + REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix != tree.end()); + REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix->first == x); + REQUIRE_ORFAIL(ok_flag, catch_flag, find_ix->second == x*10); + + ++i; + } + + REQUIRE_ORFAIL(ok_flag, catch_flag, tree.size() == n); + + log.end_scope(); + + return ok_flag; + } /*random_lookups*/ + + /* Require: + * - tree has keys [0..n-1], where n=treẹsize() + * - tree value at key k is dvalue+10*k + */ + static bool + check_ordinal_lookup(std::uint32_t dvalue, + bool catch_flag, + Tree const & tree) + { + using xo::scope; + using xo::xtag; + + /* -> false if/when verification fails */ + bool ok_flag = true; + + xo::scope log(XO_DEBUG(catch_flag)); + + std::size_t const n = tree.size(); + std::size_t i = 0; + + log && log("tree with size n", xtag("n", n)); + + for (std::size_t i=0; ifirst == i)); + REQUIRE_ORFAIL(ok_flag, catch_flag, (ix->second == 10*i + dvalue)); + } + + log.end_scope(); + + return ok_flag; + } /*check_ordinal_lookup*/ + + /* Require: + * - tree has keys [0..n-1], where n=treẹsize() + * - tree values at key k is dvalue+10*k + * + * catch_flag. true -> log to console + interact with catch2 + * false -> verify iteration behavior for return code + */ + static bool + check_bidirectional_iterator(uint32_t dvalue, + bool catch_flag, + Tree const & tree) + { + using xo::scope; + using xo::xtag; + + /* -> false if/when verification fails */ + bool ok_flag = true; + + std::size_t const n = tree.size(); + + xo::scope log(XO_DEBUG(catch_flag)); + + log && log("tree with size n", xtag("n", n)); + + { + std::size_t i = 0; + + auto end_ix = tree.end(); + + log && log(xtag("end_ix", end_ix)); + + auto begin_ix = tree.begin(); + auto ix = begin_ix; + + int last_key = -1; + + while (ix != end_ix) { + log && log("forward loop top", + xtag("i", i), + xtag("ix", ix)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, ix->first == i); + REQUIRE_ORFAIL(ok_flag, catch_flag, ix->second == dvalue + 10*i); + if(i > 0) { + REQUIRE_ORFAIL(ok_flag, catch_flag, ix->first > last_key); + } + last_key = ix->first; + ++i; + ++ix; + + log && log("forward loop bottom", + xtag("last_key", last_key), + xtag("next ix", ix)); + } + + /* should have visited exactly n locations */ + REQUIRE_ORFAIL(ok_flag, catch_flag, i == n); + REQUIRE_ORFAIL(ok_flag, catch_flag, ix == end_ix); + + log && log(xtag("ix", ix), xtag("begin_ix", begin_ix)); + + /* now run iterator backwards, + * starting from "one past the end" + */ + if(ix != begin_ix) { + do { + --i; + --ix; + + log && log("forward backup", + xtag("i", i), + xtag("ix", ix)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, ix.is_dereferenceable()); + + log && log(xtag("ix.first", (*ix).first)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, (*ix).first == i); + } while (ix != begin_ix); + } + + /* should have visited exactly n locations in reverse */ + REQUIRE_ORFAIL(ok_flag, catch_flag, i == 0); + } + + /* ----- reverse iterators ----- */ + + { + std::int64_t i = n - 1; + + auto rbegin_ix = tree.rbegin(); + auto rend_ix = tree.rend(); + + auto rix = rbegin_ix; + + int last_key = -1; + + while (rix != rend_ix) { + log && log("reverse loop top", + xtag("i", i), + xtag("rix", rix)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, rix->first == i); + REQUIRE_ORFAIL(ok_flag, catch_flag, rix->second == dvalue + 10*i); + if (i < n-1) { + REQUIRE_ORFAIL(ok_flag, catch_flag, rix->first < last_key); + } + last_key = rix->first; + --i; + ++rix; + + log && log("reverse loop bottom", + xtag("last_key", last_key), + xtag("next ix", rix)); + } + + /* should have visited exactly n locations */ + REQUIRE_ORFAIL(ok_flag, catch_flag, i == -1); + + log && log(xtag("rbegin_ix", rbegin_ix)); + + /* now run reverse iterator backwrds, + * starting from "one before the beginning" + */ + if (rix != rbegin_ix) { + do { + ++i; + --rix; + + log && log("reverse backup", + xtag("i", i), + xtag("rix", rix), + xtag("rix.first", rix->first)); + + REQUIRE_ORFAIL(ok_flag, catch_flag, (*rix).first == i); + } while (rix != rbegin_ix); + } + + /* should have visited exactly n locations in reversê2 */ + REQUIRE_ORFAIL(ok_flag, catch_flag, i == n - 1); + } + + log.end_scope(); + + return ok_flag; + } /*check_bidirectional_iterator*/ + }; /*TreeUtil*/ +} /*namespace utest*/ + +/* end random_tree_ops.hpp */ diff --git a/utest/redblacktree.cpp b/utest/redblacktree.cpp new file mode 100644 index 00000000..658abf64 --- /dev/null +++ b/utest/redblacktree.cpp @@ -0,0 +1,248 @@ +/* @file redblacktree.cpp */ + +#include "random_tree_ops.hpp" +#include "xo/tree/RedBlackTree.hpp" +#include + +namespace { + using xo::tree::RedBlackTree; + using xo::tree::SumReduce; + using xo::tree::OrdinalReduce; + using xo::tree::NullReduce; + using xo::rng::xoshiro256ss; + + using utest::Util; + using utest::TreeUtil; + + using xo::scope; + using xo::scope_setup; + using xo::xtag; + + //using RbTree = RedBlackTree>; + using RbTree = RedBlackTree>; + +#ifdef OBSOLETE + /* Require: + * - rbtree has keys [0..n-1] where n=rbtree.size(), + * - rbtree value at key k is dvalue+10*k + */ + void + check_ordinal_lookup(uint32_t dvalue, + RbTree const & rbtree) + { + size_t const n = rbtree.size(); + size_t i = 0; + + for(size_t i=0; ifirst == i); + } + } /*check_ordinal_lookup*/ +#endif + + /* check that RedBlackTree<>::find_sum_glb() works as advertised. + * + * partial sums of v[j] for j<=i will be: + * + * (i+1) . i + * 10 . --------- + ((i+1) . dvalue) + * 2 + * + * = (i+1).(5.i + dvalue) + * + * Require: + * - rbtree has keys [0..n-1], where n=rbtree.size() + * - rbtree value at key k is dvalue+10*k + */ + void + check_reduced_sum(uint32_t dvalue, + RbTree const & rbtree) + { + size_t const n = rbtree.size(); + + for(size_t i = 0; i < n; ++i) { + /* compute reduction up to key=i */ + double reduced_upto + = rbtree.reduce_lub(i /*key*/, + true /*is_closed*/); + + double reduced = (i+1) * (5*i + dvalue); + + INFO(tostr(xtag("i", i), xtag("n", n), + xtag("tree.reduced_upto", reduced_upto), + xtag("reduced", reduced), + xtag("dvalue", dvalue))); + + auto glb_ix = rbtree.cfind_sum_glb(reduced); + + REQUIRE(reduced_upto == reduced); + + REQUIRE(glb_ix.is_dereferenceable()); + /* glb_ix is truth-y */ + REQUIRE(glb_ix); + + REQUIRE(glb_ix->first == i); + } + } /*check_reduced_sum*/ + +#ifdef OBSOLETE + /* Require: + * - *p_rbtree has keys [0..n-1], where n=rbtree.size() + * - for each key k, associated value is 10*k + */ + void + random_lookups(RbTree const & rbtree, + xoshiro256ss * p_rgen) + { + REQUIRE(rbtree.verify_ok()); + + size_t n = rbtree.size(); + std::vector u + = Util::random_permutation(n, p_rgen); + + /* lookup keys in permutation order */ + uint32_t i = 1; + for (uint32_t x : u) { + INFO(tostr(xtag("i", i), xtag("n", n), xtag("x", x))); + + REQUIRE(rbtree[x] == x*10); + REQUIRE(rbtree.verify_ok()); + REQUIRE(rbtree.size() == n); + ++i; + } + + REQUIRE(rbtree.size() == n); + } /*random_lookups*/ +#endif + + /* Require: + * - *p_rbtree has keys [0..n-1], where n=rbtree.size() + * - for each key k, associated value is 10*k + * + * Promise: + * - for each key k, associated value is dvalue + 10*k + */ + void + random_updates(uint32_t dvalue, + RbTree * p_rbtree, + xoshiro256ss * p_rgen) + { + REQUIRE(p_rbtree->verify_ok()); + + std::size_t n = p_rbtree->size(); + std::vector u + = Util::random_permutation(n, p_rgen); + + /* update key/value pairs in permutation order */ + uint32_t i = 1; + for (uint32_t x : u) { + REQUIRE((*p_rbtree)[x] == x*10); + + (*p_rbtree)[x] = dvalue + 10*x; + + REQUIRE((*p_rbtree)[x] == dvalue + 10*x); + REQUIRE(p_rbtree->verify_ok()); + /* assignment to existing key does not change tree size */ + REQUIRE(p_rbtree->size() == n); + ++i; + } + + REQUIRE(p_rbtree->size() == n); + } /*random_updates_1*/ + + TEST_CASE("rbtree", "[redblacktree]") { + RbTree rbtree; + + std::uint64_t seed = 14950349842636922572UL; + /* can reseed from /dev/urandom with: */ + //arc4random_buf(&seed, sizeof(seed)); + + auto rgen = xo::rng::xoshiro256ss(seed); + + /* perform a series of tests with increasing scale */ + for(std::uint32_t n=0; n<=1024; ) { + bool ok_flag = false; + + for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) { + /* attention=0: + * - no logging + * - detect assertion failures, but don't report them to catch2 + * attention=1: + * - only runs if failure detected with attention=0 + * - full logging + * - report to catch + */ + + bool debug_flag = (attention == 1); + + scope log(XO_DEBUG2(debug_flag, "rbtree")); + log && log(xtag("size", n)); + + ok_flag = true; + + if (n == 0) { + /* check iteration on empty tree */ + ok_flag &= TreeUtil::check_bidirectional_iterator(0 /*dvalue - not used*/, + debug_flag, + rbtree); + } else { + /* insert [0..n-1] in random order */ + ok_flag &= TreeUtil::random_inserts(n, debug_flag, &rgen, &rbtree); + + /* TODO: generalize remaining helpers; share with bplustree unit test */ + + /* check iterator traverses [0..n-1] in both directions (using ++ and --) */ + ok_flag &= TreeUtil::check_ordinal_lookup(0 /*dvalue*/, + debug_flag, + rbtree); + /* verify end-to-end iteration */ + ok_flag &= TreeUtil::check_bidirectional_iterator(0, + debug_flag, + rbtree); + /* verify behavior of .reduce_lub(), .find_sum_glb() */ + check_reduced_sum(0, rbtree); + /* verify behavior of read-only variant of operator[] */ + ok_flag &= TreeUtil::random_lookups(debug_flag, + rbtree, + &rgen); + + /* verify that lookups didn't somehow disturb tree contents */ + ok_flag &= TreeUtil::check_ordinal_lookup(0 /*dvalue*/, + debug_flag, + rbtree); + + ok_flag &= TreeUtil::check_bidirectional_iterator(0, + debug_flag, + rbtree); + /* verify update via read/write operator[] */ + random_updates(10000, &rbtree, &rgen); + + /* verify that updates changed tree contents in expected way */ + ok_flag &= TreeUtil::check_ordinal_lookup(10000 /*dvalue*/, + debug_flag, + rbtree); + + /* verify end-to-end iteration */ + ok_flag &= TreeUtil::check_bidirectional_iterator(10000, + debug_flag, + rbtree); + /* verify behavior of .reduce_lub(), .find_sum_glb() */ + check_reduced_sum(10000, rbtree); + /* verify behavior of read/write variant of operator[] */ + ok_flag &= TreeUtil::random_removes(debug_flag, &rgen, &rbtree); + } + + log.end_scope(); + } + + if (n == 0) + n = 1; + else + n = 2*n; + } + } /*TEST_CASE(rbtree)*/ +} /*namespace*/ + +/* end redblacktree.cpp */ diff --git a/utest/tree_utest_main.cpp b/utest/tree_utest_main.cpp new file mode 100644 index 00000000..73ae4bab --- /dev/null +++ b/utest/tree_utest_main.cpp @@ -0,0 +1,7 @@ +/* @file tree_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#define CATCH_CONFIG_ENABLE_BENCHMARKING +#include "catch2/catch.hpp" + +/* end tree_utest_main.cpp */ From 0c76f5f40aa758f4e221e3f3861161af399ac31b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:24:09 -0400 Subject: [PATCH 0121/2524] + .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..49f711e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# typical build directories +build +ccov From 5e16be0b5adeecef371bbc149539e71678576c1d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:28:47 -0400 Subject: [PATCH 0122/2524] rename xo-tree -> xo-ordinaltree --- CMakeLists.txt | 9 ++++----- ...treeConfig.cmake.in => xo_ordinaltreeConfig.cmake.in} | 0 include/xo/{tree => ordinaltree}/BplusTree.hpp | 0 include/xo/{tree => ordinaltree}/RedBlackTree.hpp | 0 .../xo/{tree => ordinaltree}/bplustree/BplusTreeUtil.hpp | 0 .../xo/{tree => ordinaltree}/bplustree/GenericNode.hpp | 0 .../xo/{tree => ordinaltree}/bplustree/InternalNode.hpp | 0 include/xo/{tree => ordinaltree}/bplustree/Iterator.hpp | 0 .../xo/{tree => ordinaltree}/bplustree/IteratorUtil.hpp | 0 include/xo/{tree => ordinaltree}/bplustree/LeafNode.hpp | 0 include/xo/{tree => ordinaltree}/bplustree/Lhs.hpp | 0 .../{tree => ordinaltree}/bplustree/bplustree_tags.hpp | 0 utest/bplustree.cpp | 2 +- utest/redblacktree.cpp | 2 +- 14 files changed, 6 insertions(+), 7 deletions(-) rename cmake/{xo_treeConfig.cmake.in => xo_ordinaltreeConfig.cmake.in} (100%) rename include/xo/{tree => ordinaltree}/BplusTree.hpp (100%) rename include/xo/{tree => ordinaltree}/RedBlackTree.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/BplusTreeUtil.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/GenericNode.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/InternalNode.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/Iterator.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/IteratorUtil.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/LeafNode.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/Lhs.hpp (100%) rename include/xo/{tree => ordinaltree}/bplustree/bplustree_tags.hpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fc26356..cde16d98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ -# xo-tree/CMakeLists.txt +# xo-ordinaltree/CMakeLists.txt cmake_minimum_required(VERSION 3.10) -project(xo_tree VERSION 0.1) +project(xo_ordinaltree VERSION 0.1) enable_language(CXX) # common XO macros (see github:Rconybea/xo-cmake) @@ -15,7 +15,7 @@ include(xo_macros/code-coverage) enable_testing() # enable code coverage for all executables+libraries # (when configured with -DCODE_COVERAGE=ON) -# +# add_code_coverage() add_code_coverage_all_targets( EXCLUDE @@ -33,7 +33,7 @@ xo_toplevel_compile_options() add_subdirectory(utest) -set(SELF_LIB xo_tree) +set(SELF_LIB xo_ordinaltree) add_library(${SELF_LIB} INTERFACE) xo_include_headeronly_options2(${SELF_LIB}) @@ -49,4 +49,3 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # input dependencies xo_dependency_headeronly(${SELF_LIB} randomgen) - diff --git a/cmake/xo_treeConfig.cmake.in b/cmake/xo_ordinaltreeConfig.cmake.in similarity index 100% rename from cmake/xo_treeConfig.cmake.in rename to cmake/xo_ordinaltreeConfig.cmake.in diff --git a/include/xo/tree/BplusTree.hpp b/include/xo/ordinaltree/BplusTree.hpp similarity index 100% rename from include/xo/tree/BplusTree.hpp rename to include/xo/ordinaltree/BplusTree.hpp diff --git a/include/xo/tree/RedBlackTree.hpp b/include/xo/ordinaltree/RedBlackTree.hpp similarity index 100% rename from include/xo/tree/RedBlackTree.hpp rename to include/xo/ordinaltree/RedBlackTree.hpp diff --git a/include/xo/tree/bplustree/BplusTreeUtil.hpp b/include/xo/ordinaltree/bplustree/BplusTreeUtil.hpp similarity index 100% rename from include/xo/tree/bplustree/BplusTreeUtil.hpp rename to include/xo/ordinaltree/bplustree/BplusTreeUtil.hpp diff --git a/include/xo/tree/bplustree/GenericNode.hpp b/include/xo/ordinaltree/bplustree/GenericNode.hpp similarity index 100% rename from include/xo/tree/bplustree/GenericNode.hpp rename to include/xo/ordinaltree/bplustree/GenericNode.hpp diff --git a/include/xo/tree/bplustree/InternalNode.hpp b/include/xo/ordinaltree/bplustree/InternalNode.hpp similarity index 100% rename from include/xo/tree/bplustree/InternalNode.hpp rename to include/xo/ordinaltree/bplustree/InternalNode.hpp diff --git a/include/xo/tree/bplustree/Iterator.hpp b/include/xo/ordinaltree/bplustree/Iterator.hpp similarity index 100% rename from include/xo/tree/bplustree/Iterator.hpp rename to include/xo/ordinaltree/bplustree/Iterator.hpp diff --git a/include/xo/tree/bplustree/IteratorUtil.hpp b/include/xo/ordinaltree/bplustree/IteratorUtil.hpp similarity index 100% rename from include/xo/tree/bplustree/IteratorUtil.hpp rename to include/xo/ordinaltree/bplustree/IteratorUtil.hpp diff --git a/include/xo/tree/bplustree/LeafNode.hpp b/include/xo/ordinaltree/bplustree/LeafNode.hpp similarity index 100% rename from include/xo/tree/bplustree/LeafNode.hpp rename to include/xo/ordinaltree/bplustree/LeafNode.hpp diff --git a/include/xo/tree/bplustree/Lhs.hpp b/include/xo/ordinaltree/bplustree/Lhs.hpp similarity index 100% rename from include/xo/tree/bplustree/Lhs.hpp rename to include/xo/ordinaltree/bplustree/Lhs.hpp diff --git a/include/xo/tree/bplustree/bplustree_tags.hpp b/include/xo/ordinaltree/bplustree/bplustree_tags.hpp similarity index 100% rename from include/xo/tree/bplustree/bplustree_tags.hpp rename to include/xo/ordinaltree/bplustree/bplustree_tags.hpp diff --git a/utest/bplustree.cpp b/utest/bplustree.cpp index 5dac55a8..d7ca3aaf 100644 --- a/utest/bplustree.cpp +++ b/utest/bplustree.cpp @@ -3,7 +3,7 @@ #define CATCH_CONFIG_ENABLE_BENCHMARKING #include "random_tree_ops.hpp" -#include "xo/tree/BplusTree.hpp" +#include "xo/ordinaltree/BplusTree.hpp" #include "randomgen/random_seed.hpp" #include "indentlog/scope.hpp" #include "catch2/catch.hpp" diff --git a/utest/redblacktree.cpp b/utest/redblacktree.cpp index 658abf64..f33db805 100644 --- a/utest/redblacktree.cpp +++ b/utest/redblacktree.cpp @@ -1,7 +1,7 @@ /* @file redblacktree.cpp */ #include "random_tree_ops.hpp" -#include "xo/tree/RedBlackTree.hpp" +#include "xo/ordinaltree/RedBlackTree.hpp" #include namespace { From 8c1bbb09356ce896c103cc4da02186da2528d9bf Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:32:58 -0400 Subject: [PATCH 0123/2524] xo-cmake: + xo_dependency_headeronly + xo_dependency --- cmake/xo_cxx.cmake | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 66ac7962..84ca488e 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -219,11 +219,22 @@ endmacro() # # dep: name of required dependency, e.g. indentlog # -macro(xo_internal_dependency target dep) +macro(xo_dependency target dep) find_package(${dep} CONFIG REQUIRED) target_link_libraries(${target} PUBLIC ${dep}) endmacro() +macro(xo_internal_dependency target dep) + xo_dependency(target dep) +endmacro() + +# dependency on a header-only library +# +macro(xo_dependency_headeronly target dep) + find_package(${dep} CONFIG REQUIRED) + target_link_libraries(${target} INTERFACE ${dep}) +endmacro() + # dependency on namespaced target # e.g. # add_library(foo ..) or add_executable(foo ...) From ad7ea74f482b26ff4d272761ae4acfcfb0898c01 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:35:15 -0400 Subject: [PATCH 0124/2524] bugfix: macro variables in xo_internal_dependency! --- cmake/xo_cxx.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 84ca488e..afdbc443 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -225,7 +225,7 @@ macro(xo_dependency target dep) endmacro() macro(xo_internal_dependency target dep) - xo_dependency(target dep) + xo_dependency(${target} ${dep}) endmacro() # dependency on a header-only library From 58903a6feee5b6e4627aac059177aa686f9461dc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:43:04 -0400 Subject: [PATCH 0125/2524] github: provide builder workflow --- .github/workflows/main.yml | 118 +++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..fa2b06e4 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,118 @@ + +name: build xo-ordinaltree + xo dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + +# # ---------------------------------------------------------------- +# +# - name: Clone subsys +# uses: actions/checkout@v3 +# with: +# repository: Rconybea/subsys +# path: repo/subsys +# +# - name: Configure subsys +# # configure cmake for subsys in dedicated build directory. +# run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys +# +# - name: Build subsys +# run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} +# +# - name: Install subsys +# # install into ${{github.workspace}}/local +# run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Configure self (xo-ordinaltree) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_xo_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (xo_ordinaltree) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_xo_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Test self (xo_ordinaltree) + working-directory: ${{github.workspace}}/build_xo_ordinaltree + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From b33b39e8fb7da923b2657292f2d4e5574a4a78ba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:45:20 -0400 Subject: [PATCH 0126/2524] github: + randomgen dep in workflow --- .github/workflows/main.yml | 46 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fa2b06e4..65548a75 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -62,24 +62,24 @@ jobs: # install into ${{github.workspace}}/local run: cmake --install ${{github.workspace}}/build_indentlog -# # ---------------------------------------------------------------- -# -# - name: Clone subsys -# uses: actions/checkout@v3 -# with: -# repository: Rconybea/subsys -# path: repo/subsys -# -# - name: Configure subsys -# # configure cmake for subsys in dedicated build directory. -# run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys -# -# - name: Build subsys -# run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} -# -# - name: Install subsys -# # install into ${{github.workspace}}/local -# run: cmake --install ${{github.workspace}}/build_subsys + # ---------------------------------------------------------------- + + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/loal repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen # ---------------------------------------------------------------- @@ -105,14 +105,14 @@ jobs: - name: Configure self (xo-ordinaltree) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_xo_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - - name: Build self (xo_ordinaltree) + - name: Build self (xo-ordinaltree) # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build_xo_ordinaltree --config ${{env.BUILD_TYPE}} + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} - - name: Test self (xo_ordinaltree) - working-directory: ${{github.workspace}}/build_xo_ordinaltree + - name: Test self (xo-ordinaltree) + working-directory: ${{github.workspace}}/build_ordinaltree # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} From d1f7ae24d26df2ce67ccdb6c75d77aa84d6e47e1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:48:10 -0400 Subject: [PATCH 0127/2524] github: need lib/cmake path for ordinaltree --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 65548a75..ae2ccd41 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -72,7 +72,7 @@ jobs: - name: Configure randomgen # configure cmake for randomgen in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/loal repo/randomgen + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/loal repo/randomgen - name: Build randomgen run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} From 5b4858e075e73c22391ce1c2a336e21422bfafcc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 13:54:43 -0400 Subject: [PATCH 0128/2524] github: + libbsd-dev dep --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ae2ccd41..6026c4ba 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,6 +26,10 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + - name: Install libbsd-dev + # provides arc4random_buf in randomgen + run: sudo apt-get install -y libbsd-dev + # ---------------------------------------------------------------- - name: Clone xo-cmake From a26a6e4656e2ed45be533a7a70c866647a8793d7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 15:50:40 -0400 Subject: [PATCH 0129/2524] random: use get_random() instead of arc4random_buf() for non-bsd builds --- include/randomgen/random_seed.hpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/include/randomgen/random_seed.hpp b/include/randomgen/random_seed.hpp index 5273ddeb..016f612f 100644 --- a/include/randomgen/random_seed.hpp +++ b/include/randomgen/random_seed.hpp @@ -1,9 +1,14 @@ /* @file random_seed.hpp */ -#include "indentlog/print/array.hpp" +//#include "indentlog/print/array.hpp" #include #include #include +#ifdef _BSD_SOURCE +# include +#else +# include +#endif namespace xo { namespace rng { @@ -20,10 +25,22 @@ namespace xo { */ template void random_seed(T * p_seed) { +# ifdef _BSD_SOURCE /* NOTE: arc4random_buf() works on darwin/nix; * probably need to do something else on intel linux */ - arc4random_buf(p_seed, sizeof(*p_seed)); + ::arc4random_buf(p_seed, sizeof(*p_seed)); +# else + /* avail flags: GRND_RANDOM | GRND_NONBLOCK */ + while (::getrandom(p_seed, sizeof(*p_seed), 0) == -1) { + if (errno == EINTR) { + /* interrupted by signal, try again */ + continue; + } else { + break; + } + } +# endif } /*random_seed*/ template From d7316a2ef079ff4fc2299dc0230064ace4fa8c52 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 15:56:40 -0400 Subject: [PATCH 0130/2524] github: typo: loal -> local --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6026c4ba..14fe1134 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -76,7 +76,7 @@ jobs: - name: Configure randomgen # configure cmake for randomgen in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/loal repo/randomgen + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen - name: Build randomgen run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} From 9a2fc0605be5b736efc6ab3d545487eae84110c0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 16:01:48 -0400 Subject: [PATCH 0131/2524] randomgen: + print.hpp --- include/randomgen/print.hpp | 7 +++++++ include/randomgen/random_seed.hpp | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 include/randomgen/print.hpp diff --git a/include/randomgen/print.hpp b/include/randomgen/print.hpp new file mode 100644 index 00000000..e9860ab0 --- /dev/null +++ b/include/randomgen/print.hpp @@ -0,0 +1,7 @@ +/* @file print.hpp */ + +#pragma once + +#include "indentlog/print/array.hpp" + +/* end print.hpp */ diff --git a/include/randomgen/random_seed.hpp b/include/randomgen/random_seed.hpp index 016f612f..67246c01 100644 --- a/include/randomgen/random_seed.hpp +++ b/include/randomgen/random_seed.hpp @@ -1,6 +1,5 @@ /* @file random_seed.hpp */ -//#include "indentlog/print/array.hpp" #include #include #include From cfef8198d469e434ac50013018c0b851d554c79e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 4 Oct 2023 16:02:52 -0400 Subject: [PATCH 0132/2524] utest: use randomgen/print.hpp --- utest/bplustree.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/utest/bplustree.cpp b/utest/bplustree.cpp index d7ca3aaf..24115e4b 100644 --- a/utest/bplustree.cpp +++ b/utest/bplustree.cpp @@ -5,6 +5,7 @@ #include "random_tree_ops.hpp" #include "xo/ordinaltree/BplusTree.hpp" #include "randomgen/random_seed.hpp" +#include "randomgen/print.hpp" #include "indentlog/scope.hpp" #include "catch2/catch.hpp" From d148f3d51fcf7f53903e122b531daadffb31cb07 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 16:39:40 -0400 Subject: [PATCH 0133/2524] tidy: remove dup decl --- utest/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 291f6d16..8d142b9b 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -20,7 +20,7 @@ target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) # since version file will be in build directory, need that directory # to also be included in compiler's include path # -xo_include_options2(${SELF_EXECUTABLE_NAME}) +#xo_include_options2(${SELF_EXECUTABLE_NAME}) #target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC # ${PROJECT_SOURCE_DIR} # ${PROJECT_BINARY_DIR}) From c18c848179e10ab770a4c11c2b1a16985bdea70d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 16:39:53 -0400 Subject: [PATCH 0134/2524] tidy: c++ indentation --- include/reflect/TaggedPtr.hpp | 182 +++++++++++++++++----------------- 1 file changed, 90 insertions(+), 92 deletions(-) diff --git a/include/reflect/TaggedPtr.hpp b/include/reflect/TaggedPtr.hpp index 653c8ad7..610cb00f 100644 --- a/include/reflect/TaggedPtr.hpp +++ b/include/reflect/TaggedPtr.hpp @@ -7,119 +7,117 @@ #include namespace xo { -namespace reflect { - class TaggedRcptr; /* see [reflect/TaggedRcptr.hpp] */ + namespace reflect { + class TaggedRcptr; /* see [reflect/TaggedRcptr.hpp] */ - class TaggedPtr { - public: - TaggedPtr(TypeDescr td, void * x) : td_{td}, address_{x} {} + class TaggedPtr { + public: + TaggedPtr(TypeDescr td, void * x) : td_{td}, address_{x} {} - static TaggedPtr universal_null() { return TaggedPtr(nullptr, nullptr); } + 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 - */ + /* would be clean to put make() here; + * however it leads to cyclic #include paths, + * so put it elsewhere + */ #ifdef NOT_USING - template - static TaggedPtr make(T * x) { return TaggedPtr(Reflect::require(), x); } + template + static TaggedPtr make(T * x) { return TaggedPtr(Reflect::require(), x); } #endif - /* visit an object tree. calls preorder_visit_fn() on tp, - * and all objects reachable directly-or-indirectly from tp. - * will call preorder_visit_fn() multiple times if there are multiple paths - * to a node. - * - * require: no cycles in object graph -- undefined behavior if a cycle is present - */ - template - static void visit_tree_preorder(TaggedPtr tp, Fn && preorder_visit_fn) { - using std::uint32_t; + /* visit an object tree. calls preorder_visit_fn() on tp, + * and all objects reachable directly-or-indirectly from tp. + * will call preorder_visit_fn() multiple times if there are multiple paths + * to a node. + * + * require: no cycles in object graph -- undefined behavior if a cycle is present + */ + template + static void visit_tree_preorder(TaggedPtr tp, Fn && preorder_visit_fn) { + using std::uint32_t; - preorder_visit_fn(tp); + preorder_visit_fn(tp); - for(uint32_t i = 0, n = tp.n_child(); i < n; ++i) { - visit_tree_preorder(tp.get_child(i), preorder_visit_fn); - } - } /*visit_tree_preorder*/ - - /* visit object graph. calls preorder_visit_fn() on tp in depth-first - * order. detects and silently prunes duplicate/cyclic references. - */ - template - static void visit_graph(TaggedPtr tp, Fn && visit_fn) { - std::unordered_set visited_set; + 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_graph_aux(tp, visit_fn, &visited_set); - } /*visit_graph*/ + /* visit object graph. calls preorder_visit_fn() on tp in depth-first + * order. detects and silently prunes duplicate/cyclic references. + */ + template + static void visit_graph(TaggedPtr tp, Fn && visit_fn) { + std::unordered_set visited_set; - TypeDescr td() const { return td_; } - void * address() const { return address_; } - - void assign_td(TypeDescr x) { td_ = x; } - void assign_address(void * x) { address_ = x; } + visit_graph_aux(tp, visit_fn, &visited_set); + } /*visit_graph*/ - 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(); } + TypeDescr td() const { return td_; } + void * address() const { return address_; } + + void assign_td(TypeDescr x) { td_ = x; } + void assign_address(void * x) { address_ = x; } + + bool is_universal_null() const { return (td_ == nullptr) && (address_ == nullptr); } + bool is_vector() const { return td_ && td_->is_vector(); } + bool is_struct() const { return td_ && td_->is_struct(); } - /* returns pointer-to-T, if in fact this tagged pointer is understood - * to refer to a T-instance; otherwise nullptr - */ - template - T * recover_native() const { return this->td_->recover_native(this->address_); } + /* returns pointer-to-T, if in fact this tagged pointer is understood + * to refer to a T-instance; otherwise nullptr + */ + template + T * recover_native() const { return this->td_->recover_native(this->address_); } - uint32_t n_child() const { - return this->td_->n_child(this->address_); - } /*n_child*/ + 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*/ + 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); - } + /* require: + * - .is_struct() is true + */ + std::string const & struct_member_name(uint32_t i) const { + return this->td_->struct_member_name(i); + } - private: - template - static void visit_graph_aux(TaggedPtr tp, - Fn && visit_fn, - std::unordered_set * p_visited_set) - { - if (tp.address() == nullptr) - return; - - if (p_visited_set->find(tp.address()) == p_visited_set->end()) { - p_visited_set->insert(tp.address()); + private: + template + static void visit_graph_aux(TaggedPtr tp, + Fn && visit_fn, + std::unordered_set * p_visited_set) + { + if (tp.address() == nullptr) + return; - 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*/ + if (p_visited_set->find(tp.address()) == p_visited_set->end()) { + p_visited_set->insert(tp.address()); - private: - friend class TaggedRcptr; + visit_fn(tp); - 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*/ + 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*/ -} /*namespace reflect*/ + 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 */ - - From af1d2f3535f8e87bd8befb46c046bb73f2e4440d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 16:40:03 -0400 Subject: [PATCH 0135/2524] bugfix: cmake config template --- cmake/reflectConfig.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/reflectConfig.cmake.in b/cmake/reflectConfig.cmake.in index e13a2c54..9c15f36a 100644 --- a/cmake/reflectConfig.cmake.in +++ b/cmake/reflectConfig.cmake.in @@ -1,4 +1,4 @@ @PACKAGE_INIT@ -include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From ba95d0a40c91f89a6a49841191aaee34bd0148bb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 16:40:18 -0400 Subject: [PATCH 0136/2524] cosmetic: drop cmake message --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0402062d..40c2c41d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,6 @@ cmake_minimum_required(VERSION 3.10) -message(CMAKE_VERSION=${CMAKE_VERSION}) - project(reflect VERSION 0.1) enable_language(CXX) From 6be9037f100b7646dc359003600a4ef6faae04ae Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 16:53:47 -0400 Subject: [PATCH 0137/2524] reflect: insert xo/ subdir into include path --- include/{ => xo}/reflect/CMakeLists.txt | 0 .../{ => xo}/reflect/EstablishTypeDescr.hpp | 38 ++++---- include/{ => xo}/reflect/Metatype.hpp | 0 include/{ => xo}/reflect/Reflect.hpp | 62 ++++++------- include/{ => xo}/reflect/SelfTagging.hpp | 6 +- include/{ => xo}/reflect/StructReflector.hpp | 90 +++++++++---------- include/{ => xo}/reflect/TaggedPtr.hpp | 3 +- include/{ => xo}/reflect/TaggedRcptr.hpp | 50 +++++------ include/{ => xo}/reflect/TypeDescr.hpp | 2 +- include/{ => xo}/reflect/TypeDescrExtra.hpp | 2 +- include/{ => xo}/reflect/TypeDrivenMap.hpp | 0 include/{ => xo}/reflect/atomic/AtomicTdx.hpp | 2 +- include/{ => xo}/reflect/init_reflect.hpp | 0 .../{ => xo}/reflect/pointer/PointerTdx.hpp | 4 +- .../{ => xo}/reflect/struct/StructMember.hpp | 29 +++--- include/{ => xo}/reflect/struct/StructTdx.hpp | 7 +- include/{ => xo}/reflect/vector/VectorTdx.hpp | 34 ++++--- utest/StructReflector.test.cpp | 8 +- utest/StructTdx.test.cpp | 2 +- utest/VectorTdx.test.cpp | 10 +-- 20 files changed, 172 insertions(+), 177 deletions(-) rename include/{ => xo}/reflect/CMakeLists.txt (100%) rename include/{ => xo}/reflect/EstablishTypeDescr.hpp (60%) rename include/{ => xo}/reflect/Metatype.hpp (100%) rename include/{ => xo}/reflect/Reflect.hpp (84%) rename include/{ => xo}/reflect/SelfTagging.hpp (88%) rename include/{ => xo}/reflect/StructReflector.hpp (64%) rename include/{ => xo}/reflect/TaggedPtr.hpp (98%) rename include/{ => xo}/reflect/TaggedRcptr.hpp (67%) rename include/{ => xo}/reflect/TypeDescr.hpp (99%) rename include/{ => xo}/reflect/TypeDescrExtra.hpp (98%) rename include/{ => xo}/reflect/TypeDrivenMap.hpp (100%) rename include/{ => xo}/reflect/atomic/AtomicTdx.hpp (96%) rename include/{ => xo}/reflect/init_reflect.hpp (100%) rename include/{ => xo}/reflect/pointer/PointerTdx.hpp (96%) rename include/{ => xo}/reflect/struct/StructMember.hpp (92%) rename include/{ => xo}/reflect/struct/StructTdx.hpp (95%) rename include/{ => xo}/reflect/vector/VectorTdx.hpp (75%) diff --git a/include/reflect/CMakeLists.txt b/include/xo/reflect/CMakeLists.txt similarity index 100% rename from include/reflect/CMakeLists.txt rename to include/xo/reflect/CMakeLists.txt diff --git a/include/reflect/EstablishTypeDescr.hpp b/include/xo/reflect/EstablishTypeDescr.hpp similarity index 60% rename from include/reflect/EstablishTypeDescr.hpp rename to include/xo/reflect/EstablishTypeDescr.hpp index 2b0e25a7..32e546d1 100644 --- a/include/reflect/EstablishTypeDescr.hpp +++ b/include/xo/reflect/EstablishTypeDescr.hpp @@ -5,8 +5,8 @@ #pragma once -#include "reflect/TypeDescr.hpp" -#include "reflect/TaggedPtr.hpp" +#include "TypeDescr.hpp" +#include "TaggedPtr.hpp" namespace xo { namespace reflect { @@ -26,28 +26,28 @@ namespace xo { template static TypeDescrW establish() { - TypeDescrW td = TypeDescrBase::require(&typeid(T), - type_name(), - nullptr); + TypeDescrW td = TypeDescrBase::require(&typeid(T), + type_name(), + nullptr); #ifdef NOT_USING - std::function to_self_tp; + std::function to_self_tp; - if (std::is_base_of_v) { - /* T is a descendant of SelfTagging (or T = SelfTagging); - * use SelfTagging.self_tp() - */ - to_self_tp = [](void * x) { return reinterpret_cast(x)->self_tp(); }; - } else { - /* T is not a descendant of SelfTagging. - * want to return - */ - to_self_tp = [td](void * x) { return TaggedPtr(td, x); }; - } + if (std::is_base_of_v) { + /* T is a descendant of SelfTagging (or T = SelfTagging); + * use SelfTagging.self_tp() + */ + to_self_tp = [](void * x) { return reinterpret_cast(x)->self_tp(); }; + } else { + /* T is not a descendant of SelfTagging. + * want to return + */ + to_self_tp = [td](void * x) { return TaggedPtr(td, x); }; + } - td->assign_to_self_tp(to_self_tp); + td->assign_to_self_tp(to_self_tp); #endif - return td; + return td; } }; /*EstablishTypeDescr*/ diff --git a/include/reflect/Metatype.hpp b/include/xo/reflect/Metatype.hpp similarity index 100% rename from include/reflect/Metatype.hpp rename to include/xo/reflect/Metatype.hpp diff --git a/include/reflect/Reflect.hpp b/include/xo/reflect/Reflect.hpp similarity index 84% rename from include/reflect/Reflect.hpp rename to include/xo/reflect/Reflect.hpp index 73ef68f3..a6e6fe5e 100644 --- a/include/reflect/Reflect.hpp +++ b/include/xo/reflect/Reflect.hpp @@ -5,12 +5,12 @@ #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 "SelfTagging.hpp" +#include "EstablishTypeDescr.hpp" +#include "atomic/AtomicTdx.hpp" +#include "pointer/PointerTdx.hpp" +#include "vector/VectorTdx.hpp" +#include "struct/StructTdx.hpp" #include "refcnt/Refcounted.hpp" #include #include @@ -22,7 +22,7 @@ namespace xo { class EstablishTdx { public: static std::unique_ptr make() { return AtomicTdx::make(); } - }; /*EstablishTdx*/ + }; /*EstablishTdx*/ // ----- xo::ref::rp ----- @@ -34,7 +34,7 @@ namespace xo { }; /*EstablishTdx*/ // ----- std::array ----- - + /* definition provide after decl for Reflect {} below */ template class EstablishTdx> { @@ -73,11 +73,11 @@ namespace xo { class TaggedPtrMaker { public: static TaggedPtr make_tp(SelfTagging * x) { - return x->self_tp(); + return x->self_tp(); } /*make_tp*/ static TaggedRcptr make_rctp(SelfTagging * x) { - return x->self_tp(); + return x->self_tp(); } /*make_rctp*/ }; /*TaggedPtrMaker*/ @@ -113,35 +113,35 @@ namespace xo { * implemented in specialized header (like [reflect/struct/VectorTdx.hpp]) to * refer to reflection info for T without having to pull in all the * headers needed to properly reflect T (like this [reflect/Reflect.hpp]) - * + * */ template static TypeDescrW require() { - TypeDescrW retval_td = EstablishTypeDescr::establish(); + TypeDescrW retval_td = EstablishTypeDescr::establish(); - /* mark TypeDescr for T as complete (even though it isn't quite yet), - * so that when we encounter recursive types, reflection terminates. - * For example consider type resulting from code like - * - * typename T; - * using T = std::vector; - * - */ - if (retval_td->mark_complete()) { - /* control here on 2nd+later calls to require(). - * in principle can immediately short-circuit. - */ - } else { - /* control comes here the first time require() runs */ + /* mark TypeDescr for T as complete (even though it isn't quite yet), + * so that when we encounter recursive types, reflection terminates. + * For example consider type resulting from code like + * + * typename T; + * using T = std::vector; + * + */ + if (retval_td->mark_complete()) { + /* control here on 2nd+later calls to require(). + * in principle can immediately short-circuit. + */ + } else { + /* control comes here the first time require() runs */ - auto final_tdx = EstablishTdx::make(); + auto final_tdx = EstablishTdx::make(); - retval_td->assign_tdextra(std::move(final_tdx)); + retval_td->assign_tdextra(std::move(final_tdx)); - /* also need to require for each child */ - } + /* also need to require for each child */ + } - return retval_td; + return retval_td; } /*require*/ /* Use: diff --git a/include/reflect/SelfTagging.hpp b/include/xo/reflect/SelfTagging.hpp similarity index 88% rename from include/reflect/SelfTagging.hpp rename to include/xo/reflect/SelfTagging.hpp index 29517e33..e188b5c2 100644 --- a/include/reflect/SelfTagging.hpp +++ b/include/xo/reflect/SelfTagging.hpp @@ -5,9 +5,9 @@ #pragma once -#include "refcnt/Refcounted.hpp" -#include "reflect/TypeDescr.hpp" -#include "reflect/TaggedRcptr.hpp" +#include "Refcounted.hpp" +#include "TypeDescr.hpp" +#include "TaggedRcptr.hpp" namespace xo { namespace reflect { diff --git a/include/reflect/StructReflector.hpp b/include/xo/reflect/StructReflector.hpp similarity index 64% rename from include/reflect/StructReflector.hpp rename to include/xo/reflect/StructReflector.hpp index f2237578..750b8669 100644 --- a/include/reflect/StructReflector.hpp +++ b/include/xo/reflect/StructReflector.hpp @@ -2,10 +2,10 @@ #pragma once -#include "reflect/Reflect.hpp" -#include "reflect/TypeDescr.hpp" -#include "reflect/struct/StructMember.hpp" -#include "reflect/struct/StructTdx.hpp" +#include "Reflect.hpp" +#include "TypeDescr.hpp" +#include "struct/StructMember.hpp" +#include "struct/StructTdx.hpp" #include namespace xo { @@ -16,7 +16,7 @@ namespace xo { template struct SelfTagger { static TaggedPtr self_tp(void * object) { - return (reinterpret_cast(object))->self_tp(); + return (reinterpret_cast(object))->self_tp(); } }; @@ -35,7 +35,7 @@ namespace xo { * REFLECT_LITERAL_MEMBER(sr, y_); * * // optional: regardless, reflection will be completed when sr goes out of scope - * sr.require_complete(); + * sr.require_complete(); */ template class StructReflector { @@ -45,68 +45,68 @@ namespace xo { public: StructReflector() : td_{EstablishTypeDescr::establish()} {} ~StructReflector() { - this->require_complete(); + this->require_complete(); } - + bool is_complete() const { return s_reflected_flag; } bool is_incomplete() const { return !s_reflected_flag; } template void reflect_member(std::string const & member_name, - MemberT OwnerT::* member_addr) { + MemberT OwnerT::* member_addr) { - auto accessor - (GeneralStructMemberAccessor::make(member_addr)); + auto accessor + (GeneralStructMemberAccessor::make(member_addr)); - /* used to do this in GeneralStructMemberAccessor<> ctor, - * but that introduces #include cycle - */ - Reflect::require(); + /* used to do this in GeneralStructMemberAccessor<> ctor, + * but that introduces #include cycle + */ + Reflect::require(); - this->member_v_.emplace_back(member_name, std::move(accessor)); + this->member_v_.emplace_back(member_name, std::move(accessor)); } /*reflect_member*/ void require_complete() { - if(!s_reflected_flag) { - s_reflected_flag = true; + if(!s_reflected_flag) { + s_reflected_flag = true; - constexpr bool have_to_self_tp = std::is_base_of_v; + constexpr bool have_to_self_tp = std::is_base_of_v; - /* if self-tagging, can use .self_tp() to get most-derived tagged pointer */ - auto to_self_tp_fn - = ([](void * object) - { - return SelfTagger::self_tp(object); - }); + /* if self-tagging, can use .self_tp() to get most-derived tagged pointer */ + auto to_self_tp_fn + = ([](void * object) + { + return SelfTagger::self_tp(object); + }); - auto tdx = StructTdx::make(std::move(this->member_v_), - have_to_self_tp, - to_self_tp_fn); + auto tdx = StructTdx::make(std::move(this->member_v_), + have_to_self_tp, + to_self_tp_fn); - this->td_->assign_tdextra(std::move(tdx)); - } + this->td_->assign_tdextra(std::move(tdx)); + } } /*complete*/ - + template void adopt_ancestors() { - assert(Reflect::is_reflected()); + assert(Reflect::is_reflected()); - TypeDescr ancestor_td = Reflect::require(); + TypeDescr ancestor_td = Reflect::require(); - /* requires that reflection of AncestorT has completed */ - { - assert(ancestor_td->is_struct()); - assert(ancestor_td->complete_flag()); - } + /* 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); + /* 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()); - } + this->member_v_.push_back(member.for_descendant()); + } } /*adopt_ancestors*/ private: diff --git a/include/reflect/TaggedPtr.hpp b/include/xo/reflect/TaggedPtr.hpp similarity index 98% rename from include/reflect/TaggedPtr.hpp rename to include/xo/reflect/TaggedPtr.hpp index 610cb00f..7bb520aa 100644 --- a/include/reflect/TaggedPtr.hpp +++ b/include/xo/reflect/TaggedPtr.hpp @@ -2,8 +2,7 @@ #pragma once -#include "reflect/TypeDescr.hpp" -//#include "reflect/EstablishTypeDescr.hpp" +#include "TypeDescr.hpp" #include namespace xo { diff --git a/include/reflect/TaggedRcptr.hpp b/include/xo/reflect/TaggedRcptr.hpp similarity index 67% rename from include/reflect/TaggedRcptr.hpp rename to include/xo/reflect/TaggedRcptr.hpp index 9ca8b15f..e31cffb3 100644 --- a/include/reflect/TaggedRcptr.hpp +++ b/include/xo/reflect/TaggedRcptr.hpp @@ -5,7 +5,7 @@ #pragma once -#include "reflect/TaggedPtr.hpp" +#include "TaggedPtr.hpp" // causes #include cycle, reflect/Reflect.hpp includes this header //#include "reflect/Reflect.hpp" #include "refcnt/Refcounted.hpp" @@ -20,22 +20,22 @@ namespace xo { class TaggedRcptr : public TaggedPtr { public: using Refcount = ref::Refcount; - + public: TaggedRcptr(TypeDescr td, Refcount * x) : TaggedPtr(td, x) { - ref::intrusive_ptr_add_ref(x); + ref::intrusive_ptr_add_ref(x); } TaggedRcptr(TaggedRcptr const & x) : TaggedPtr(x) { - ref::intrusive_ptr_add_ref(x.rc_address()); + 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); + /* 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()); + ref::intrusive_ptr_release(this->rc_address()); } /* causes #include cycle, see [reflect/Reflect.hpp] */ @@ -46,37 +46,37 @@ namespace xo { #endif Refcount * rc_address() const { - return reinterpret_cast(this->address()); + return reinterpret_cast(this->address()); } /*rc_address*/ TaggedRcptr & operator=(TaggedRcptr const & rhs) { - Refcount * x = rhs.rc_address(); - Refcount * old = this->rc_address(); + Refcount * x = rhs.rc_address(); + Refcount * old = this->rc_address(); - TaggedPtr::operator=(rhs); + TaggedPtr::operator=(rhs); - if (x != old) { - intrusive_ptr_release(old); - intrusive_ptr_add_ref(x); - } + if (x != old) { + intrusive_ptr_release(old); + intrusive_ptr_add_ref(x); + } - return *this; + 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_); + /* 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; + 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; diff --git a/include/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp similarity index 99% rename from include/reflect/TypeDescr.hpp rename to include/xo/reflect/TypeDescr.hpp index 08614071..da59c007 100644 --- a/include/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -3,7 +3,7 @@ #pragma once //#include "reflect/atomic/AtomicTdx.hpp" -#include "reflect/TypeDescrExtra.hpp" +#include "TypeDescrExtra.hpp" #include "cxxutil/demangle.hpp" #include #include diff --git a/include/reflect/TypeDescrExtra.hpp b/include/xo/reflect/TypeDescrExtra.hpp similarity index 98% rename from include/reflect/TypeDescrExtra.hpp rename to include/xo/reflect/TypeDescrExtra.hpp index 2d2be7fa..824cd5d4 100644 --- a/include/reflect/TypeDescrExtra.hpp +++ b/include/xo/reflect/TypeDescrExtra.hpp @@ -2,7 +2,7 @@ #pragma once -#include "reflect/Metatype.hpp" +#include "Metatype.hpp" #include /* note: this file #include'd into TypeDescr.hpp */ #include diff --git a/include/reflect/TypeDrivenMap.hpp b/include/xo/reflect/TypeDrivenMap.hpp similarity index 100% rename from include/reflect/TypeDrivenMap.hpp rename to include/xo/reflect/TypeDrivenMap.hpp diff --git a/include/reflect/atomic/AtomicTdx.hpp b/include/xo/reflect/atomic/AtomicTdx.hpp similarity index 96% rename from include/reflect/atomic/AtomicTdx.hpp rename to include/xo/reflect/atomic/AtomicTdx.hpp index 7b2e042d..98e10cfe 100644 --- a/include/reflect/atomic/AtomicTdx.hpp +++ b/include/xo/reflect/atomic/AtomicTdx.hpp @@ -2,7 +2,7 @@ #pragma once -#include "reflect/TypeDescrExtra.hpp" +#include "xo/reflect/TypeDescrExtra.hpp" //#include "reflect/TaggedPtr.hpp" #include diff --git a/include/reflect/init_reflect.hpp b/include/xo/reflect/init_reflect.hpp similarity index 100% rename from include/reflect/init_reflect.hpp rename to include/xo/reflect/init_reflect.hpp diff --git a/include/reflect/pointer/PointerTdx.hpp b/include/xo/reflect/pointer/PointerTdx.hpp similarity index 96% rename from include/reflect/pointer/PointerTdx.hpp rename to include/xo/reflect/pointer/PointerTdx.hpp index d2d3b868..327456cf 100644 --- a/include/reflect/pointer/PointerTdx.hpp +++ b/include/xo/reflect/pointer/PointerTdx.hpp @@ -5,8 +5,8 @@ #pragma once -#include "reflect/TypeDescrExtra.hpp" -#include "reflect/EstablishTypeDescr.hpp" +#include "xo/reflect/TypeDescrExtra.hpp" +#include "xo/reflect/EstablishTypeDescr.hpp" #include "indentlog/scope.hpp" namespace xo { diff --git a/include/reflect/struct/StructMember.hpp b/include/xo/reflect/struct/StructMember.hpp similarity index 92% rename from include/reflect/struct/StructMember.hpp rename to include/xo/reflect/struct/StructMember.hpp index eaddf3b3..3c7bab62 100644 --- a/include/reflect/struct/StructMember.hpp +++ b/include/xo/reflect/struct/StructMember.hpp @@ -2,10 +2,9 @@ #pragma once -#include "reflect/TypeDescr.hpp" -#include "reflect/EstablishTypeDescr.hpp" -//#include "reflect/Reflect.hpp" -#include "reflect/TaggedPtr.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include "xo/reflect/EstablishTypeDescr.hpp" +#include "xo/reflect/TaggedPtr.hpp" #include #include @@ -14,7 +13,7 @@ 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; @@ -30,7 +29,7 @@ namespace reflect { * .member_td() => Reflect::require(); */ virtual TypeDescr member_td() const = 0; - + /* get address of a particular member, given parent address */ virtual void * address(void * struct_addr) const = 0; @@ -63,7 +62,7 @@ namespace reflect { public: GeneralStructMemberAccessor(Memptr memptr) : member_td_{EstablishTypeDescr::establish()}, - memptr_{memptr} {} + memptr_{memptr} {} GeneralStructMemberAccessor(GeneralStructMemberAccessor const & x) = default; virtual ~GeneralStructMemberAccessor() = default; @@ -78,7 +77,7 @@ namespace reflect { return &(owner_addr->*memptr_); } /*address_impl*/ - + // ----- Inherited from AbstractStructMemberAccessor ----- #ifdef OBSOLETE @@ -102,7 +101,7 @@ namespace reflect { virtual std::unique_ptr clone() const override { return std::unique_ptr - (new GeneralStructMemberAccessor(*this)); + (new GeneralStructMemberAccessor(*this)); } /*clone*/ private: @@ -142,7 +141,7 @@ namespace reflect { static std::unique_ptr adopt(std::unique_ptr ancestor_accessor) { return std::unique_ptr - (new AncestorStructMemberAccessor(std::move(ancestor_accessor))); + (new AncestorStructMemberAccessor(std::move(ancestor_accessor))); } /*adopt*/ void * address_impl(StructT * self_addr) const { @@ -171,7 +170,7 @@ namespace reflect { virtual std::unique_ptr clone() const override { return std::unique_ptr - (new AncestorStructMemberAccessor(std::move(this->ancestor_accessor_->clone()))); + (new AncestorStructMemberAccessor(std::move(this->ancestor_accessor_->clone()))); } /*clone*/ private: @@ -186,11 +185,11 @@ namespace reflect { public: StructMember() = default; StructMember(std::string const & name, - std::unique_ptr accessor) + std::unique_ptr accessor) : member_name_{name}, accessor_{std::move(accessor)} {} StructMember(StructMember && x) : member_name_{std::move(x.member_name_)}, - accessor_{std::move(x.accessor_)} {} + accessor_{std::move(x.accessor_)} {} static StructMember null(); @@ -210,8 +209,8 @@ namespace reflect { assert(EstablishTypeDescr::establish() == this->get_struct_td()); return StructMember(this->member_name(), - std::move(AncestorStructMemberAccessor::adopt - (std::move(this->accessor_->clone())))); + std::move(AncestorStructMemberAccessor::adopt + (std::move(this->accessor_->clone())))); } /*for_descendant*/ StructMember & operator=(StructMember && x) { diff --git a/include/reflect/struct/StructTdx.hpp b/include/xo/reflect/struct/StructTdx.hpp similarity index 95% rename from include/reflect/struct/StructTdx.hpp rename to include/xo/reflect/struct/StructTdx.hpp index 5e170ef7..594de2d5 100644 --- a/include/reflect/struct/StructTdx.hpp +++ b/include/xo/reflect/struct/StructTdx.hpp @@ -2,9 +2,10 @@ #pragma once -#include "reflect/TypeDescrExtra.hpp" -#include "reflect/TaggedPtr.hpp" -#include "reflect/struct/StructMember.hpp" +#include "xo/reflect/TypeDescrExtra.hpp" +#include "xo/reflect/TaggedPtr.hpp" +#include "StructMember.hpp" +//#include "xo/reflect/struct/StructMember.hpp" #include #include #include diff --git a/include/reflect/vector/VectorTdx.hpp b/include/xo/reflect/vector/VectorTdx.hpp similarity index 75% rename from include/reflect/vector/VectorTdx.hpp rename to include/xo/reflect/vector/VectorTdx.hpp index 4f3309c2..944c451c 100644 --- a/include/reflect/vector/VectorTdx.hpp +++ b/include/xo/reflect/vector/VectorTdx.hpp @@ -5,12 +5,8 @@ #pragma once -#include "reflect/TypeDescrExtra.hpp" -//#include "reflect/TaggedPtr.hpp" -#include "reflect/EstablishTypeDescr.hpp" -//#include "reflect/TaggedPtr.hpp" -//#include -//#include +#include "xo/reflect/TypeDescrExtra.hpp" +#include "xo/reflect/EstablishTypeDescr.hpp" namespace xo { namespace reflect { @@ -42,19 +38,19 @@ namespace xo { using target_t = VectorT; static std::unique_ptr make() { - return std::unique_ptr(new StlVectorTdx()); + return std::unique_ptr(new StlVectorTdx()); } /*make*/ virtual uint32_t n_child(void * object) const override { - target_t * vec = reinterpret_cast(object); + target_t * vec = reinterpret_cast(object); - return vec->size(); + return vec->size(); } /*n_child*/ virtual TaggedPtr child_tp(uint32_t i, void * object) const override { - target_t * vec = reinterpret_cast(object); + target_t * vec = reinterpret_cast(object); - return establish_most_derived_tp(&((*vec)[i])); + return establish_most_derived_tp(&((*vec)[i])); } /*child_tp*/ }; /*StlVectorTdx*/ @@ -66,9 +62,9 @@ namespace xo { template using StdArrayTdx = StlVectorTdx>; - + // ----- std::vector ----- - + /* coordinates with EstablishTdx>::make() * see [reflect/Reflect.hpp] */ @@ -76,21 +72,21 @@ namespace xo { class StdVectorTdx : public VectorTdx { public: using target_t = std::vector; - + static std::unique_ptr make() { - return std::unique_ptr(new StdVectorTdx()); + return std::unique_ptr(new StdVectorTdx()); } /*make*/ virtual uint32_t n_child(void * object) const override { - target_t * vec = reinterpret_cast(object); + target_t * vec = reinterpret_cast(object); - return vec->size(); + return vec->size(); } /*n_child*/ virtual TaggedPtr child_tp(uint32_t i, void * object) const override { - target_t * vec = reinterpret_cast(object); + target_t * vec = reinterpret_cast(object); - return establish_most_derived_tp(&((*vec)[i])); + return establish_most_derived_tp(&((*vec)[i])); } }; /*StdVectorTdx*/ } /*namespace reflect*/ diff --git a/utest/StructReflector.test.cpp b/utest/StructReflector.test.cpp index 5bf87ade..4ab0772d 100644 --- a/utest/StructReflector.test.cpp +++ b/utest/StructReflector.test.cpp @@ -3,12 +3,12 @@ * author: Roland Conybeare, Aug 2022 */ -#include "reflect/Reflect.hpp" -#include "reflect/StructReflector.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/reflect/StructReflector.hpp" #include #define STRINGIFY(x) #x - + namespace xo { using xo::reflect::Reflect; using xo::reflect::TaggedPtr; @@ -129,7 +129,7 @@ namespace xo { REQUIRE(tp.get_child(2).td() == Reflect::require()); REQUIRE(tp.get_child(2).address() == &(recd1.z_)); - + REQUIRE(tp.get_child(3).is_universal_null()); REQUIRE(tp.get_child(3).td() == nullptr); REQUIRE(tp.get_child(3).address() == nullptr); diff --git a/utest/StructTdx.test.cpp b/utest/StructTdx.test.cpp index c20e2139..cb845e18 100644 --- a/utest/StructTdx.test.cpp +++ b/utest/StructTdx.test.cpp @@ -3,7 +3,7 @@ * author: Roland Conybeare, Aug 2022 */ -#include "reflect/Reflect.hpp" +#include "xo/reflect/Reflect.hpp" #include namespace xo { diff --git a/utest/VectorTdx.test.cpp b/utest/VectorTdx.test.cpp index 3836b4f7..00f30a48 100644 --- a/utest/VectorTdx.test.cpp +++ b/utest/VectorTdx.test.cpp @@ -3,7 +3,7 @@ * author: Roland Conybeare, Aug 2022 */ -#include "reflect/Reflect.hpp" +#include "xo/reflect/Reflect.hpp" #include namespace xo { @@ -81,7 +81,7 @@ namespace xo { REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); REQUIRE(tp0.recover_native() == &(v[0])); REQUIRE(tp0.n_child() == 0); - + TaggedPtr tp1 = tp.get_child(1); REQUIRE(tp1.td()->complete_flag()); @@ -91,7 +91,7 @@ namespace xo { REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); REQUIRE(tp1.recover_native() == &(v[1])); REQUIRE(tp1.n_child() == 0); - } /*TEST(std-vector-reflect-two)*/ + } /*TEST(std-vector-reflect-two)*/ // ----- std::array ----- @@ -163,7 +163,7 @@ namespace xo { REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); REQUIRE(tp0.recover_native() == &(v[0])); REQUIRE(tp0.n_child() == 0); - + TaggedPtr tp1 = tp.get_child(1); REQUIRE(tp1.td()->complete_flag()); @@ -173,7 +173,7 @@ namespace xo { REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); REQUIRE(tp1.recover_native() == &(v[1])); REQUIRE(tp1.n_child() == 0); - } /*TEST(std-array-reflect-two)*/ + } /*TEST(std-array-reflect-two)*/ } /*namespace ut*/ } /*namespace xo*/ From 08d4ceeb329ffacffba13b8fb19b95b7d3b1f090 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 16:54:25 -0400 Subject: [PATCH 0138/2524] provide for xo/ subdir in include path --- cmake/xo_cxx.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index afdbc443..e540bc75 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -62,7 +62,8 @@ macro(xo_include_headeronly_options2 target) ${target} INTERFACE $ # e.g. for #include "indentlog/scope.hpp" $ - $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED] + $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect $ $ # e.g. for generated .hpp files ) @@ -123,7 +124,8 @@ macro(xo_include_options2 target) ${target} PUBLIC $ # e.g. for #include "indentlog/scope.hpp" $ - $ # e.g. for #include "Refcounted.hpp" in refcnt/src + $ # e.g. for #include "Refcounted.hpp" in refcnt/src [DEPRECATED] + $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect $ $ # e.g. for generated .hpp files ) From 035c187cd1503ce004b795a9d54af4568485dfc4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 16:59:19 -0400 Subject: [PATCH 0139/2524] cmake: remove not-working guards on CMAKE_EXPORT_COMPILE_COMMANDS --- cmake/xo_cxx.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index e540bc75..7eaaaff9 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -37,9 +37,9 @@ macro(xo_toplevel_compile_options) # writes ${PROJECT_BINARY_DIR}/compile_commands.json; # (symlink from toplevel git dir to tell LSP how to build) # - if(NOT DEFINED CMAKE_EXPORT_COMPILE_COMMANDS) - set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") - endif() + # note: trying to protect this with if(NOT DEFINED ..) is /not/ effective + # + set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") if(NOT CMAKE_INSTALL_RPATH) set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING From c8280cb8c4e00d0c0973d245726b1c31ed18cd25 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 17:05:08 -0400 Subject: [PATCH 0140/2524] document compile_commands.json symlink + .gitignore --- .gitignore | 2 ++ README.md | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6637741b..eff45bd2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .cache # typical cmake build directory (source-tree-nephew) build*/* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/README.md b/README.md index 0cd50f02..3c5d34be 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ $ cd reflect $ mkdir build $ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer $ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. $ make $ make install @@ -12,8 +13,14 @@ $ make install ### build for unit test coverage ``` -$ cd refcnt +$ cd xo-reflect $ mkdir build-ccov $ cd build-ccov $ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. ``` + +### LSP support +``` +$ cd xo-reflect +$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` From 9718be824b6270f445ec79f62c6dfc89ba1dde9c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 17:05:28 -0400 Subject: [PATCH 0141/2524] cosmetic: indentation --- include/xo/reflect/Reflect.hpp | 352 +++++++++++++++---------------- include/xo/reflect/TypeDescr.hpp | 119 ++++++----- 2 files changed, 235 insertions(+), 236 deletions(-) diff --git a/include/xo/reflect/Reflect.hpp b/include/xo/reflect/Reflect.hpp index a6e6fe5e..478078ad 100644 --- a/include/xo/reflect/Reflect.hpp +++ b/include/xo/reflect/Reflect.hpp @@ -17,219 +17,219 @@ #include // for std::pair<> namespace xo { - namespace reflect { - template - class EstablishTdx { - public: - static std::unique_ptr make() { return AtomicTdx::make(); } - }; /*EstablishTdx*/ + namespace reflect { + template + class EstablishTdx { + public: + static std::unique_ptr make() { return AtomicTdx::make(); } + }; /*EstablishTdx*/ - // ----- xo::ref::rp ----- + // ----- xo::ref::rp ----- - /* definition provide after decl for Reflect {} below */ - template - class EstablishTdx> { - public: - static std::unique_ptr make(); - }; /*EstablishTdx*/ + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ - // ----- std::array ----- + // ----- std::array ----- - /* definition provide after decl for Reflect {} below */ - template - class EstablishTdx> { - public: - static std::unique_ptr make(); - }; /*EstablishTdx*/ + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ - // ----- std::vector ----- + // ----- std::vector ----- - /* definition provide after decl for Reflect {} below */ - template - class EstablishTdx> { - public: - static std::unique_ptr make(); - }; /*EstablishTdx*/ + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ - // ----- std::pair ----- + // ----- std::pair ----- - /* definition provide after decl for Reflect {} below */ - template - class EstablishTdx> { - public: - static std::unique_ptr make(); - }; /*EstablishTdx*/ + /* definition provide after decl for Reflect {} below */ + template + class EstablishTdx> { + public: + static std::unique_ptr make(); + }; /*EstablishTdx*/ - // ----- MakeTagged ----- + // ----- MakeTagged ----- - template - class TaggedPtrMaker { - public: - static TaggedPtr make_tp(T * x); - static TaggedRcptr make_rctp(T * x); - }; + template + class TaggedPtrMaker { + public: + static TaggedPtr make_tp(T * x); + static TaggedRcptr make_rctp(T * x); + }; - template<> - class TaggedPtrMaker { - public: - static TaggedPtr make_tp(SelfTagging * x) { - return x->self_tp(); - } /*make_tp*/ + template<> + class TaggedPtrMaker { + public: + static TaggedPtr make_tp(SelfTagging * x) { + return x->self_tp(); + } /*make_tp*/ - static TaggedRcptr make_rctp(SelfTagging * x) { - return x->self_tp(); - } /*make_rctp*/ - }; /*TaggedPtrMaker*/ + static TaggedRcptr make_rctp(SelfTagging * x) { + return x->self_tp(); + } /*make_rctp*/ + }; /*TaggedPtrMaker*/ - // ----- Reflect ----- + // ----- Reflect ----- - class Reflect { - public: - /* Use: - * using mytype = ...; - * if (Reflect::is_reflected()) { ... } - */ - template - static bool is_reflected() { return TypeDescrBase::is_reflected(&typeid(T)); } + class Reflect { + public: + /* Use: + * using mytype = ...; + * if (Reflect::is_reflected()) { ... } + */ + template + static bool is_reflected() { return TypeDescrBase::is_reflected(&typeid(T)); } - /* Use: - * using mytype = ...; - * TypeDescrW td = Reflect::require(); - * - * Note: - * To avoid cyclic header dependencies - * (between EstablishTypeDescr.hpp <-> {vector/VectorTdx.hpp etc.}, - * we use a 2-stage setup process: - * - * 1. EstablishTypeDescr::establish() creates a TypeDescr* object - * with lowest-common-denominator .tdextra AtomicTdx. - * (see [reflect/EstablishTypeDescr.hpp]) - * - * 2. Reflect::require() upgrades .tdextra to suitable implementation - * depending on T; this means also need to visit reflection info - * (TypeDescr objects) for nested types to upgrade them too. - * - * This allows template-fu for a compound type (like std::vector), - * implemented in specialized header (like [reflect/struct/VectorTdx.hpp]) to - * refer to reflection info for T without having to pull in all the - * headers needed to properly reflect T (like this [reflect/Reflect.hpp]) - * - */ - template - static TypeDescrW require() { - TypeDescrW retval_td = EstablishTypeDescr::establish(); + /* Use: + * using mytype = ...; + * TypeDescrW td = Reflect::require(); + * + * Note: + * To avoid cyclic header dependencies + * (between EstablishTypeDescr.hpp <-> {vector/VectorTdx.hpp etc.}, + * we use a 2-stage setup process: + * + * 1. EstablishTypeDescr::establish() creates a TypeDescr* object + * with lowest-common-denominator .tdextra AtomicTdx. + * (see [reflect/EstablishTypeDescr.hpp]) + * + * 2. Reflect::require() upgrades .tdextra to suitable implementation + * depending on T; this means also need to visit reflection info + * (TypeDescr objects) for nested types to upgrade them too. + * + * This allows template-fu for a compound type (like std::vector), + * implemented in specialized header (like [reflect/struct/VectorTdx.hpp]) to + * refer to reflection info for T without having to pull in all the + * headers needed to properly reflect T (like this [reflect/Reflect.hpp]) + * + */ + template + static TypeDescrW require() { + TypeDescrW retval_td = EstablishTypeDescr::establish(); - /* mark TypeDescr for T as complete (even though it isn't quite yet), - * so that when we encounter recursive types, reflection terminates. - * For example consider type resulting from code like - * - * typename T; - * using T = std::vector; - * - */ - if (retval_td->mark_complete()) { - /* control here on 2nd+later calls to require(). - * in principle can immediately short-circuit. - */ - } else { - /* control comes here the first time require() runs */ + /* mark TypeDescr for T as complete (even though it isn't quite yet), + * so that when we encounter recursive types, reflection terminates. + * For example consider type resulting from code like + * + * typename T; + * using T = std::vector; + * + */ + if (retval_td->mark_complete()) { + /* control here on 2nd+later calls to require(). + * in principle can immediately short-circuit. + */ + } else { + /* control comes here the first time require() runs */ - auto final_tdx = EstablishTdx::make(); + auto final_tdx = EstablishTdx::make(); - retval_td->assign_tdextra(std::move(final_tdx)); + retval_td->assign_tdextra(std::move(final_tdx)); - /* also need to require for each child */ - } + /* also need to require for each child */ + } - return retval_td; - } /*require*/ + return retval_td; + } /*require*/ - /* Use: - * T * xyz = ...; - * TaggedPtr xyz_tp = Reflect::make_tp(xyz); - */ - template - static TaggedPtr make_tp(T * x) { return TaggedPtrMaker::make_tp(x); } + /* Use: + * T * xyz = ...; + * TaggedPtr xyz_tp = Reflect::make_tp(xyz); + */ + template + static TaggedPtr make_tp(T * x) { return TaggedPtrMaker::make_tp(x); } - template - static TaggedRcptr make_rctp(T * x) { return TaggedPtrMaker::make_rctp(x); } - }; /*Reflect*/ + template + static TaggedRcptr make_rctp(T * x) { return TaggedPtrMaker::make_rctp(x); } + }; /*Reflect*/ - // ----- MakeTagged ----- + // ----- MakeTagged ----- - template - TaggedPtr - TaggedPtrMaker::make_tp(T * x) { - return TaggedPtr(Reflect::require(), x); - } /*make_tp*/ + template + TaggedPtr + TaggedPtrMaker::make_tp(T * x) { + return TaggedPtr(Reflect::require(), x); + } /*make_tp*/ - template - TaggedRcptr - TaggedPtrMaker::make_rctp(T * x) { - return TaggedRcptr(Reflect::require(), x); - } /*make_rctp*/ + template + TaggedRcptr + TaggedPtrMaker::make_rctp(T * x) { + return TaggedRcptr(Reflect::require(), x); + } /*make_rctp*/ - // ----- xo::ref::rp ----- + // ----- xo::ref::rp ----- - /* declared above before - * class Reflect { .. } - */ - template - std::unique_ptr - EstablishTdx>::make() { - /* need to ensure Object is property reflected. - * - * In practice must be a class type, since has to store refcount - * + supply assoc'd incr/decr methods - */ - Reflect::require(); + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Object is property reflected. + * + * In practice must be a class type, since has to store refcount + * + supply assoc'd incr/decr methods + */ + Reflect::require(); - return RefPointerTdx>::make(); - } /*make*/ + return RefPointerTdx>::make(); + } /*make*/ - // ----- std::array ----- + // ----- std::array ----- - /* declared above before - * class Reflect { .. } - */ - template - std::unique_ptr - EstablishTdx>::make() { - /* need to ensure Element is properly reflected */ - Reflect::require(); + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Element is properly reflected */ + Reflect::require(); - return StdArrayTdx::make(); - } /*make*/ + return StdArrayTdx::make(); + } /*make*/ - // ----- std::vector ----- + // ----- std::vector ----- - /* declared above before - * class Reflect { .. } - */ - template - std::unique_ptr - EstablishTdx>::make() { - /* need to ensure Element is properly reflected */ - Reflect::require(); + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Element is properly reflected */ + Reflect::require(); - return StdVectorTdx::make(); - } /*make*/ + return StdVectorTdx::make(); + } /*make*/ - // ----- std::pair ----- + // ----- std::pair ----- - /* declared above before - * class Reflect { .. } - */ - template - std::unique_ptr - EstablishTdx>::make() { - /* need to ensure Lhs, Rhs are properly reflected */ - Reflect::require(); - Reflect::require(); + /* declared above before + * class Reflect { .. } + */ + template + std::unique_ptr + EstablishTdx>::make() { + /* need to ensure Lhs, Rhs are properly reflected */ + Reflect::require(); + Reflect::require(); - return StructTdx::pair(); - } /*make*/ - } /*namespace reflect*/ + return StructTdx::pair(); + } /*make*/ + } /*namespace reflect*/ } /*namespace xo*/ /* end Reflect.hpp */ diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index da59c007..142a3287 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -2,7 +2,6 @@ #pragma once -//#include "reflect/atomic/AtomicTdx.hpp" #include "TypeDescrExtra.hpp" #include "cxxutil/demangle.hpp" #include @@ -16,80 +15,80 @@ #include namespace xo { - namespace reflect { - class TaggedPtr; /* see [reflect/TaggedPtr.hpp] */ + 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++); } + /* 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_; } + std::uint32_t id() const { return id_; } - private: - explicit TypeId(std::uint32_t id) : id_{id} {} + private: + explicit TypeId(std::uint32_t id) : id_{id} {} - private: - static std::uint32_t s_next_id; + private: + static std::uint32_t s_next_id; - /* unique index# for this type. - * 0 reserved for sentinel - */ - std::uint32_t id_ = 0; - }; /*TypeId*/ + /* 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<<*/ + 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; + /* runtime description of a struct/class instance variable */ + class StructMember; - class TypeDescrBase; + class TypeDescrBase; - using TypeDescr = TypeDescrBase const *; - using TypeDescrW = 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; + /* convenience wrapper for a std::type_info pointer. + * works properly with pybind11, since python doens't encounter + * native type_info pointer, it won't try to delete it. + */ + class TypeInfoRef { + public: + explicit TypeInfoRef(std::type_info const * tinfo) : tinfo_{tinfo} {} + TypeInfoRef(TypeInfoRef const & x) = default; - /* use: - * TypeInfoRef tinfo = TypeInfoRef::make(); - */ - template - TypeInfoRef make() { return TypeInfoRef(&typeid(T)); } + /* use: + * TypeInfoRef tinfo = TypeInfoRef::make(); + */ + template + TypeInfoRef make() { return TypeInfoRef(&typeid(T)); } - std::size_t hash_code() const { return this->tinfo_->hash_code(); } - char const * impl_name() const { return this->tinfo_->name(); } + 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; + 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*/ + 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*/ + private: + /* native type_info object for encapsulated type */ + std::type_info const * tinfo_ = nullptr; + }; /*TypeInfoRef*/ + } /*namespace reflect*/ } /*namespace xo*/ namespace std { From 61ddb8140e4a79f728a51bec9589d85b08328031 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:02:22 -0400 Subject: [PATCH 0142/2524] refcnt: update after inserting xo into include path --- include/xo/reflect/Reflect.hpp | 2 +- include/xo/reflect/SelfTagging.hpp | 2 +- include/xo/reflect/TaggedRcptr.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/xo/reflect/Reflect.hpp b/include/xo/reflect/Reflect.hpp index 478078ad..89a75374 100644 --- a/include/xo/reflect/Reflect.hpp +++ b/include/xo/reflect/Reflect.hpp @@ -11,7 +11,7 @@ #include "pointer/PointerTdx.hpp" #include "vector/VectorTdx.hpp" #include "struct/StructTdx.hpp" -#include "refcnt/Refcounted.hpp" +#include "xo/refcnt/Refcounted.hpp" #include #include #include // for std::pair<> diff --git a/include/xo/reflect/SelfTagging.hpp b/include/xo/reflect/SelfTagging.hpp index e188b5c2..b41d4ce3 100644 --- a/include/xo/reflect/SelfTagging.hpp +++ b/include/xo/reflect/SelfTagging.hpp @@ -5,7 +5,7 @@ #pragma once -#include "Refcounted.hpp" +#include "xo/refcnt/Refcounted.hpp" #include "TypeDescr.hpp" #include "TaggedRcptr.hpp" diff --git a/include/xo/reflect/TaggedRcptr.hpp b/include/xo/reflect/TaggedRcptr.hpp index e31cffb3..3e06af64 100644 --- a/include/xo/reflect/TaggedRcptr.hpp +++ b/include/xo/reflect/TaggedRcptr.hpp @@ -8,7 +8,7 @@ #include "TaggedPtr.hpp" // causes #include cycle, reflect/Reflect.hpp includes this header //#include "reflect/Reflect.hpp" -#include "refcnt/Refcounted.hpp" +#include "xo/refcnt/Refcounted.hpp" namespace xo { namespace reflect { From d480174107a7a2c89b761c9c7a7a483055dd9656 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:03:21 -0400 Subject: [PATCH 0143/2524] refcnt: insert xo/ into include path --- include/{ => xo}/cxxutil/demangle.hpp | 0 include/{ => xo}/refcnt/Displayable.hpp | 2 +- include/{ => xo}/refcnt/Refcounted.hpp | 0 include/{ => xo}/refcnt/Unowned.hpp | 0 src/Displayable.cpp | 2 +- utest/intrusive_ptr.test.cpp | 2 +- 6 files changed, 3 insertions(+), 3 deletions(-) rename include/{ => xo}/cxxutil/demangle.hpp (100%) rename include/{ => xo}/refcnt/Displayable.hpp (95%) rename include/{ => xo}/refcnt/Refcounted.hpp (100%) rename include/{ => xo}/refcnt/Unowned.hpp (100%) diff --git a/include/cxxutil/demangle.hpp b/include/xo/cxxutil/demangle.hpp similarity index 100% rename from include/cxxutil/demangle.hpp rename to include/xo/cxxutil/demangle.hpp diff --git a/include/refcnt/Displayable.hpp b/include/xo/refcnt/Displayable.hpp similarity index 95% rename from include/refcnt/Displayable.hpp rename to include/xo/refcnt/Displayable.hpp index 74708573..5a184667 100644 --- a/include/refcnt/Displayable.hpp +++ b/include/xo/refcnt/Displayable.hpp @@ -2,7 +2,7 @@ #pragma once -#include "refcnt/Refcounted.hpp" +#include "Refcounted.hpp" namespace xo { namespace ref { diff --git a/include/refcnt/Refcounted.hpp b/include/xo/refcnt/Refcounted.hpp similarity index 100% rename from include/refcnt/Refcounted.hpp rename to include/xo/refcnt/Refcounted.hpp diff --git a/include/refcnt/Unowned.hpp b/include/xo/refcnt/Unowned.hpp similarity index 100% rename from include/refcnt/Unowned.hpp rename to include/xo/refcnt/Unowned.hpp diff --git a/src/Displayable.cpp b/src/Displayable.cpp index b8793ad3..ea839841 100644 --- a/src/Displayable.cpp +++ b/src/Displayable.cpp @@ -1,6 +1,6 @@ /* @file Displayable.cpp */ -#include "refcnt/Displayable.hpp" +#include "Displayable.hpp" namespace xo { using xo::tostr; diff --git a/utest/intrusive_ptr.test.cpp b/utest/intrusive_ptr.test.cpp index d8d756e2..f9d1f212 100644 --- a/utest/intrusive_ptr.test.cpp +++ b/utest/intrusive_ptr.test.cpp @@ -1,6 +1,6 @@ /* @file intrusive_ptr.test.cpp */ -#include "refcnt/Refcounted.hpp" +#include "Refcounted.hpp" #include "indentlog/scope.hpp" #include "catch2/catch.hpp" #include From 2b195c47f7a63f6ff5bfc3ead4eb4999ec9d7d50 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:09:21 -0400 Subject: [PATCH 0144/2524] subsys: insert xo/ into include path --- .gitignore | 2 ++ include/{ => xo}/subsys/Subsystem.hpp | 0 2 files changed, 2 insertions(+) create mode 100644 .gitignore rename include/{ => xo}/subsys/Subsystem.hpp (100%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d6bd1a25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# typical build location +build diff --git a/include/subsys/Subsystem.hpp b/include/xo/subsys/Subsystem.hpp similarity index 100% rename from include/subsys/Subsystem.hpp rename to include/xo/subsys/Subsystem.hpp From f5c60e299b6743eae84342da41184435583d5da3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:09:41 -0400 Subject: [PATCH 0145/2524] xo-reflect: update subsys include location --- include/xo/reflect/init_reflect.hpp | 2 +- src/reflect/init_reflect.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/xo/reflect/init_reflect.hpp b/include/xo/reflect/init_reflect.hpp index 00506a25..b6365632 100644 --- a/include/xo/reflect/init_reflect.hpp +++ b/include/xo/reflect/init_reflect.hpp @@ -5,7 +5,7 @@ #pragma once -#include "subsys/Subsystem.hpp" +#include "xo/subsys/Subsystem.hpp" namespace xo { /* tag to represent the reflect/ subsystem within ordered initialization */ diff --git a/src/reflect/init_reflect.cpp b/src/reflect/init_reflect.cpp index 20dd1441..d95bf2e3 100644 --- a/src/reflect/init_reflect.cpp +++ b/src/reflect/init_reflect.cpp @@ -4,7 +4,7 @@ */ #include "init_reflect.hpp" -#include "subsys/Subsystem.hpp" +#include "xo/subsys/Subsystem.hpp" namespace xo { void From 243e9573c7601e01d236ad855d76263e7cb88a81 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:21:58 -0400 Subject: [PATCH 0146/2524] randomgen: inserted xo/ into include path --- utest/bplustree.cpp | 4 ++-- utest/random_tree_ops.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/utest/bplustree.cpp b/utest/bplustree.cpp index 24115e4b..2fe68d83 100644 --- a/utest/bplustree.cpp +++ b/utest/bplustree.cpp @@ -4,8 +4,8 @@ #include "random_tree_ops.hpp" #include "xo/ordinaltree/BplusTree.hpp" -#include "randomgen/random_seed.hpp" -#include "randomgen/print.hpp" +#include "xo/randomgen/random_seed.hpp" +#include "xo/randomgen/print.hpp" #include "indentlog/scope.hpp" #include "catch2/catch.hpp" diff --git a/utest/random_tree_ops.hpp b/utest/random_tree_ops.hpp index b81601f7..890202f7 100644 --- a/utest/random_tree_ops.hpp +++ b/utest/random_tree_ops.hpp @@ -1,6 +1,6 @@ /* @file random_tree_ops.hpp **/ -#include "randomgen/xoshiro256.hpp" +#include "xo/randomgen/xoshiro256.hpp" #include "indentlog/scope.hpp" #include "indentlog/print/tag.hpp" #include "indentlog/print/vector.hpp" From b7b80ffe7502309e96f6977aac84907088619493 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:22:27 -0400 Subject: [PATCH 0147/2524] .gitignore: + .ccache --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 49f711e2..9e716afc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# lsp keeps state here +.cache # typical build directories build ccov From 1e62d358dc9c17e5c8a2d13d4bf785f138def827 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:22:44 -0400 Subject: [PATCH 0148/2524] + README.md --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..41625b26 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# ordinal tree library + +### build + install +``` +$ cd xo-ordinaltree +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` + +### build for unit test coverage +``` +$ cd xo-ordinaltree +$ mkdir build-ccov +$ cd build-ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP support +``` +$ cd xo-ordinaltree +$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` From aaba8f05080816839bc322d8ff32629a306dd78c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:33:48 -0400 Subject: [PATCH 0149/2524] drop include path --- cmake/xo_cxx.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 7eaaaff9..da93eccb 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -64,7 +64,7 @@ macro(xo_include_headeronly_options2 target) $ $ # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED] $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect - $ +# $ $ # e.g. for generated .hpp files ) @@ -126,7 +126,7 @@ macro(xo_include_options2 target) $ $ # e.g. for #include "Refcounted.hpp" in refcnt/src [DEPRECATED] $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect - $ +# $ $ # e.g. for generated .hpp files ) From 00fae469e02e42ed9c9e3f9b5aa1f4527d789cf2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:41:45 -0400 Subject: [PATCH 0150/2524] randomgen: fixing include paths for xo/ insertion --- example/ex1/ex1.cpp | 4 +--- example/ex2/ex2.cpp | 4 ++-- include/{ => xo}/randomgen/engine_concept.hpp | 0 include/{ => xo}/randomgen/print.hpp | 0 include/{ => xo}/randomgen/random_seed.hpp | 0 include/{ => xo}/randomgen/xoshiro256.hpp | 0 6 files changed, 3 insertions(+), 5 deletions(-) rename include/{ => xo}/randomgen/engine_concept.hpp (100%) rename include/{ => xo}/randomgen/print.hpp (100%) rename include/{ => xo}/randomgen/random_seed.hpp (100%) rename include/{ => xo}/randomgen/xoshiro256.hpp (100%) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 8c908fa5..0db92be4 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -1,10 +1,8 @@ /* @file ex1.cpp */ -#include "randomgen/xoshiro256.hpp" +#include "xo/randomgen/xoshiro256.hpp" #include #include -//#include -//#include using namespace xo; using namespace xo::rng; diff --git a/example/ex2/ex2.cpp b/example/ex2/ex2.cpp index c77e06ff..949b65dc 100644 --- a/example/ex2/ex2.cpp +++ b/example/ex2/ex2.cpp @@ -1,7 +1,7 @@ /* @file ex2.cpp */ -#include "randomgen/xoshiro256.hpp" -#include "randomgen/random_seed.hpp" +#include "xo/randomgen/xoshiro256.hpp" +#include "xo/randomgen/random_seed.hpp" using namespace xo; using namespace xo::rng; diff --git a/include/randomgen/engine_concept.hpp b/include/xo/randomgen/engine_concept.hpp similarity index 100% rename from include/randomgen/engine_concept.hpp rename to include/xo/randomgen/engine_concept.hpp diff --git a/include/randomgen/print.hpp b/include/xo/randomgen/print.hpp similarity index 100% rename from include/randomgen/print.hpp rename to include/xo/randomgen/print.hpp diff --git a/include/randomgen/random_seed.hpp b/include/xo/randomgen/random_seed.hpp similarity index 100% rename from include/randomgen/random_seed.hpp rename to include/xo/randomgen/random_seed.hpp diff --git a/include/randomgen/xoshiro256.hpp b/include/xo/randomgen/xoshiro256.hpp similarity index 100% rename from include/randomgen/xoshiro256.hpp rename to include/xo/randomgen/xoshiro256.hpp From 5b558ce086ccbb4861e914a676dc8212cb3042f0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:42:32 -0400 Subject: [PATCH 0151/2524] .gitignore: + compile_commands.json --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 69e11d47..e90fd723 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .cache # typical build directory build +# compile_commands.json: symlink to build directory, should be created manually +compile_commands.json From 8ba04142eaec5de963c208979c42f05fa830d7b0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:43:00 -0400 Subject: [PATCH 0152/2524] build fix: account for xo/ insertion into include path --- README.md | 18 ++++++++++++------ include/xo/refcnt/Refcounted.hpp | 4 ++-- utest/intrusive_ptr.test.cpp | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index bae1fb2c..0b53dc81 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,13 @@ see [github/Rconybea/indentlog](https://github.com/Rconybea/indentlog) ### copy `refcnt` repository locally ``` $ git clone git@github.com:rconybea/refcnt.git -$ ls -d refcnt -refcnt +$ ls -d xo-refcnt +xo-refcnt ``` ### build + install ``` -$ cd refcnt +$ cd xo-refcnt $ mkdir build $ cd build $ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. @@ -39,22 +39,28 @@ $ git clone git@github.com:rconybea/xo-nix.git $ ls -d xo-nix xo-nix $ cd xo-nix -$ nix-build -A refcnt +$ nix-build -A xo-refcnt ``` ### build for unit test coverage ``` -$ cd refcnt +$ cd xo-refcnt $ mkdir ccov $ cd ccov $ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. ``` +### LSP support +``` +$ cd xo-refcnt +$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` + ## Examples ### 1 ``` -#include "refcnt/Refcounted.hpp" +#include "xo/refcnt/Refcounted.hpp" using xo::ref::Refcounted; diff --git a/include/xo/refcnt/Refcounted.hpp b/include/xo/refcnt/Refcounted.hpp index dc069cf5..3db621a1 100644 --- a/include/xo/refcnt/Refcounted.hpp +++ b/include/xo/refcnt/Refcounted.hpp @@ -2,8 +2,8 @@ #pragma once -#include "indentlog/scope.hpp" -#include "cxxutil/demangle.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/cxxutil/demangle.hpp" //#include #include diff --git a/utest/intrusive_ptr.test.cpp b/utest/intrusive_ptr.test.cpp index f9d1f212..bc45d38c 100644 --- a/utest/intrusive_ptr.test.cpp +++ b/utest/intrusive_ptr.test.cpp @@ -1,7 +1,7 @@ /* @file intrusive_ptr.test.cpp */ #include "Refcounted.hpp" -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" #include "catch2/catch.hpp" #include #include From 1b3be7e7335d8c5d607656f75cd5a026963831d1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 18:43:49 -0400 Subject: [PATCH 0153/2524] build: account for xo/ insertion into include path --- include/xo/reflect/TypeDescr.hpp | 2 +- include/xo/reflect/pointer/PointerTdx.hpp | 2 +- src/reflect/TaggedRcptr.cpp | 2 +- src/reflect/TypeDescr.cpp | 2 +- src/reflect/struct/StructMember.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index 142a3287..d908a77b 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -3,7 +3,7 @@ #pragma once #include "TypeDescrExtra.hpp" -#include "cxxutil/demangle.hpp" +#include "xo/cxxutil/demangle.hpp" #include #include #include diff --git a/include/xo/reflect/pointer/PointerTdx.hpp b/include/xo/reflect/pointer/PointerTdx.hpp index 327456cf..037d3ee4 100644 --- a/include/xo/reflect/pointer/PointerTdx.hpp +++ b/include/xo/reflect/pointer/PointerTdx.hpp @@ -7,7 +7,7 @@ #include "xo/reflect/TypeDescrExtra.hpp" #include "xo/reflect/EstablishTypeDescr.hpp" -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" namespace xo { namespace reflect { diff --git a/src/reflect/TaggedRcptr.cpp b/src/reflect/TaggedRcptr.cpp index cc461e0c..8d68a60b 100644 --- a/src/reflect/TaggedRcptr.cpp +++ b/src/reflect/TaggedRcptr.cpp @@ -4,7 +4,7 @@ */ #include "TaggedRcptr.hpp" -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/print/tag.hpp" namespace xo { using xo::xtag; diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp index 564fec14..93a0bfc1 100644 --- a/src/reflect/TypeDescr.cpp +++ b/src/reflect/TypeDescr.cpp @@ -4,7 +4,7 @@ #include "TaggedPtr.hpp" #include "TypeDescrExtra.hpp" #include "atomic/AtomicTdx.hpp" -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" namespace xo { using xo::scope; diff --git a/src/reflect/struct/StructMember.cpp b/src/reflect/struct/StructMember.cpp index 59d69056..7f27af8c 100644 --- a/src/reflect/struct/StructMember.cpp +++ b/src/reflect/struct/StructMember.cpp @@ -4,7 +4,7 @@ */ #include "struct/StructMember.hpp" -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" #include namespace xo { From ebfbd40afc752d160ae23c1b424094f14ec66d77 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 19:12:24 -0400 Subject: [PATCH 0154/2524] bugfix: include part must supply xo/ --- include/xo/subsys/Subsystem.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/subsys/Subsystem.hpp b/include/xo/subsys/Subsystem.hpp index b9a41e18..ece72db6 100644 --- a/include/xo/subsys/Subsystem.hpp +++ b/include/xo/subsys/Subsystem.hpp @@ -5,7 +5,7 @@ #pragma once -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" #include #include #include From 14ee09a76b45a9baf64a1de9941ae5028e612e8f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 19:22:04 -0400 Subject: [PATCH 0155/2524] build: update include paths to include xo/ --- include/xo/ordinaltree/BplusTree.hpp | 6 +++--- include/xo/ordinaltree/RedBlackTree.hpp | 6 +++--- include/xo/ordinaltree/bplustree/BplusTreeUtil.hpp | 4 ++-- include/xo/ordinaltree/bplustree/InternalNode.hpp | 4 ++-- include/xo/ordinaltree/bplustree/Iterator.hpp | 2 +- include/xo/ordinaltree/bplustree/LeafNode.hpp | 2 +- utest/bplustree.cpp | 2 +- utest/random_tree_ops.hpp | 6 +++--- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/include/xo/ordinaltree/BplusTree.hpp b/include/xo/ordinaltree/BplusTree.hpp index f58b4317..7810a031 100644 --- a/include/xo/ordinaltree/BplusTree.hpp +++ b/include/xo/ordinaltree/BplusTree.hpp @@ -15,9 +15,9 @@ #include "bplustree/Iterator.hpp" #include "bplustree/Lhs.hpp" #include "bplustree/bplustree_tags.hpp" -#include "indentlog/scope.hpp" -#include "indentlog/print/tag.hpp" -#include "indentlog/print/pad.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/pad.hpp" #include /* for std::unqiue_ptr */ #include /* for std::max */ #include /* for std::numeric_limits */ diff --git a/include/xo/ordinaltree/RedBlackTree.hpp b/include/xo/ordinaltree/RedBlackTree.hpp index e9460b95..7a3ea1e4 100644 --- a/include/xo/ordinaltree/RedBlackTree.hpp +++ b/include/xo/ordinaltree/RedBlackTree.hpp @@ -5,9 +5,9 @@ #pragma once -#include "indentlog/scope.hpp" -#include "indentlog/print/pad.hpp" -#include "indentlog/print/quoted.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/pad.hpp" +#include "xo/indentlog/print/quoted.hpp" #include #include #include diff --git a/include/xo/ordinaltree/bplustree/BplusTreeUtil.hpp b/include/xo/ordinaltree/bplustree/BplusTreeUtil.hpp index f2da0263..61c91e95 100644 --- a/include/xo/ordinaltree/bplustree/BplusTreeUtil.hpp +++ b/include/xo/ordinaltree/bplustree/BplusTreeUtil.hpp @@ -4,8 +4,8 @@ #include "IteratorUtil.hpp" #include "bplustree_tags.hpp" -#include "indentlog/scope.hpp" -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tag.hpp" #include // for std::unique_ptr #include diff --git a/include/xo/ordinaltree/bplustree/InternalNode.hpp b/include/xo/ordinaltree/bplustree/InternalNode.hpp index d4dce90a..9ef4b7dd 100644 --- a/include/xo/ordinaltree/bplustree/InternalNode.hpp +++ b/include/xo/ordinaltree/bplustree/InternalNode.hpp @@ -3,8 +3,8 @@ #pragma once #include "GenericNode.hpp" -#include "indentlog/scope.hpp" -#include "indentlog/print/tostr.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tostr.hpp" #include namespace xo { diff --git a/include/xo/ordinaltree/bplustree/Iterator.hpp b/include/xo/ordinaltree/bplustree/Iterator.hpp index 90f57924..ed82f8d8 100644 --- a/include/xo/ordinaltree/bplustree/Iterator.hpp +++ b/include/xo/ordinaltree/bplustree/Iterator.hpp @@ -4,7 +4,7 @@ #include "IteratorUtil.hpp" #include "LeafNode.hpp" -#include "indentlog/print/tostr.hpp" +#include "xo/indentlog/print/tostr.hpp" namespace xo { namespace tree { diff --git a/include/xo/ordinaltree/bplustree/LeafNode.hpp b/include/xo/ordinaltree/bplustree/LeafNode.hpp index 419bd63f..bcb43469 100644 --- a/include/xo/ordinaltree/bplustree/LeafNode.hpp +++ b/include/xo/ordinaltree/bplustree/LeafNode.hpp @@ -3,7 +3,7 @@ #pragma once #include "GenericNode.hpp" -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" #include namespace xo { diff --git a/utest/bplustree.cpp b/utest/bplustree.cpp index 2fe68d83..a38fd545 100644 --- a/utest/bplustree.cpp +++ b/utest/bplustree.cpp @@ -6,7 +6,7 @@ #include "xo/ordinaltree/BplusTree.hpp" #include "xo/randomgen/random_seed.hpp" #include "xo/randomgen/print.hpp" -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" #include "catch2/catch.hpp" namespace { diff --git a/utest/random_tree_ops.hpp b/utest/random_tree_ops.hpp index 890202f7..0c6d898f 100644 --- a/utest/random_tree_ops.hpp +++ b/utest/random_tree_ops.hpp @@ -1,9 +1,9 @@ /* @file random_tree_ops.hpp **/ #include "xo/randomgen/xoshiro256.hpp" -#include "indentlog/scope.hpp" -#include "indentlog/print/tag.hpp" -#include "indentlog/print/vector.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/vector.hpp" #include "catch2/catch.hpp" #include #include From b3d4a1276efcfa9b555161b38dc7db44747e05f6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 19:27:24 -0400 Subject: [PATCH 0156/2524] build: fix include path to insert xo/ dir --- include/xo/randomgen/print.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/randomgen/print.hpp b/include/xo/randomgen/print.hpp index e9860ab0..d5b0a992 100644 --- a/include/xo/randomgen/print.hpp +++ b/include/xo/randomgen/print.hpp @@ -2,6 +2,6 @@ #pragma once -#include "indentlog/print/array.hpp" +#include "xo/indentlog/print/array.hpp" /* end print.hpp */ From e6e659bc0563ed08726216ac13929aa164488d67 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 20:14:06 -0400 Subject: [PATCH 0157/2524] + xo_pybind11_link_flags() + xo_pybind11_library() --- cmake/xo_cxx.cmake | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index da93eccb..37867d59 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -263,4 +263,46 @@ macro(xo_self_dependency target dep) endmacro() # ---------------------------------------------------------------- +# need this when linking pybind11-generated libraries # +macro(xo_pybind11_link_flags) + # see: + # 1. FAQ Build Issues Q2 + # 2. CMAKE_SHARED_LINKER_FLAGS in src/CMakeLists.txt + # 3. pybind11 cmake support, somewhere like + # [path/to/pybind11-2.9.2/ + # lib/python3.9-pybind11-2.9.2/ + # lib/python3.9/site-packages/ + # pybind11/share/cmake/ + # pybind11/pybind11Common.cmake] + # + set_property( + TARGET pybind11::python_link_helper + APPEND + PROPERTY INTERFACE_LINK_OPTIONS "$<$:LINKER:-flat_namespace>" + ) +endmacro() + +# ---------------------------------------------------------------- +# use this for a subdir that builds a python library using pybind11 +# +# expecting the following +# 1. a directory pyfoo/ -> library pyfoo +# 2. pyfoo/pyfoo.hpp.in -> pyfoo/pyfoo.hpp +# +macro(xo_pybind11_library target source_files) + configure_file(${target}.hpp.in ${target}.hpp) + + # find_package(Python..) finds python in + # /Library/Frameworks/Python.framework/... + # but we want to use python from nix + # + #find_package(Python COMPONENTS Interpreter Development REQUIRED) + # + + find_package(pybind11) + pybind11_add_module(${target} MODULE ${source_files}) + xo_pybind11_link_flags() + # use xo_install_library2() instead + #install(TARGETS ${target} DESTINATION lib) +endmacro() From 515f632cb59b459d53abc06972f9c0c7ebf67318 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:10:40 -0400 Subject: [PATCH 0158/2524] initial commit (kitbashed from kalman project) --- CMakeLists.txt | 50 +++++++++++++++++++++++++++++ README.md | 26 ++++++++++++++++ cmake/xo_pyreflectConfig.cmake.in | 4 +++ src/pyreflect/CMakeLists.txt | 11 +++++++ src/pyreflect/pyreflect.cpp | 52 +++++++++++++++++++++++++++++++ src/pyreflect/pyreflect.hpp.in | 25 +++++++++++++++ 6 files changed, 168 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/xo_pyreflectConfig.cmake.in create mode 100644 src/pyreflect/CMakeLists.txt create mode 100644 src/pyreflect/pyreflect.cpp create mode 100644 src/pyreflect/pyreflect.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..9c372fd5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,50 @@ +# xo-pyreflect/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pyreflect VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# 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 (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +# note: library=pyreflect (not xo_pyreflect) -> establishes pyreflectTargets +add_subdirectory(src/pyreflect) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} pyreflectTargets) + +# ---------------------------------------------------------------- +# install .hpp files + +#xo_install_include_tree() diff --git a/README.md b/README.md new file mode 100644 index 00000000..c020ad6e --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# python bindings for c++ reflection library (xo-reflect) + +### build + install +``` +$ cd xo-pyreflect +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` + +### build for unit test coverage +``` +$ cd xo-pyreflect +$ mkdir build-ccov +$ cd build-ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP support +``` +$ cd xo-pyreflect +$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` diff --git a/cmake/xo_pyreflectConfig.cmake.in b/cmake/xo_pyreflectConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pyreflectConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/src/pyreflect/CMakeLists.txt b/src/pyreflect/CMakeLists.txt new file mode 100644 index 00000000..9b63978f --- /dev/null +++ b/src/pyreflect/CMakeLists.txt @@ -0,0 +1,11 @@ +# xo_pyreflect/CMakeLists.txt + +set(SELF_LIB pyreflect) +set(SELF_SRCS pyreflect.cpp) + +# ---------------------------------------------------------------- +# pybind11 dep + +xo_pybind11_library(${SELF_LIB} ${SELF_SRCS}) + +xo_dependency(${SELF_LIB} reflect) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp new file mode 100644 index 00000000..3a9f81bb --- /dev/null +++ b/src/pyreflect/pyreflect.cpp @@ -0,0 +1,52 @@ +/* @file pyreflect.cpp */ + +// note: need pyreflect/ here bc pyreflect.hpp is generated, located in build directory +#include "src/pyreflect/pyreflect.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include "xo/reflect/TaggedRcptr.hpp" +#include "xo/reflect/SelfTagging.hpp" +//#include "time/Time.hpp" +//#include "xo/pyutil/pytime.hpp" +#include "xo/pyutil/pyutil.hpp" +//#include +//#include +//#include +//#include + +namespace xo { + using xo::time::utc_nanos; + using xo::ref::unowned_ptr; + using xo::ref::rp; + namespace py = pybind11; + + namespace reflect { + PYBIND11_MODULE(PYREFLECT_MODULE_NAME(), m) { + + /* note: possibly move this to pytime/ if/when we provide it */ + //py::class_(m, "utc_nanos"); + + //py::class_(m, "TypeDescr"); + py::class_>(m, "TypeDescr") + .def_static("print_reflected_types", + [](){ TypeDescrBase::print_reflected_types(std::cout); }) + .def_property_readonly("canonical_name", &TypeDescrBase::canonical_name) + .def("__repr__", &TypeDescrBase::display_string); + + /* note: this means python will use + * std::unique_ptr + * when it encounters a TaggedRcptr instance. + * Maintains refcount at cost of 2nd level of indirection. + */ + py::class_(m, "TaggedRcptr") + .def_property_readonly("td", &TaggedPtr::td) + .def("__repr__", &TaggedRcptr::display_string); + + py::class_>(m, "SelfTagging") + .def("self_tp", &SelfTagging::self_tp); + + } /*pyreflect*/ + } /*namespace reflect*/ +} /*namespace xo*/ + +/* end pyreflect.cpp */ diff --git a/src/pyreflect/pyreflect.hpp.in b/src/pyreflect/pyreflect.hpp.in new file mode 100644 index 00000000..62b64a25 --- /dev/null +++ b/src/pyreflect/pyreflect.hpp.in @@ -0,0 +1,25 @@ +/* @file pyreflect.hpp + * + * automatically generated from src/xo_pyreflect/pyreflect.hpp.in + * see src/xo_pyreflect/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYREFLECT_MODULE_NAME(), m) { ... } + */ +#define PYREFLECT_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(PYREFLECT_MODULE_NAME_STR) + */ +#define PYREFLECT_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * PYREFLECT_IMPORT_MODULE() + * replaces + * py::module_::import("pyreflect") + */ +#define PYREFLECT_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pyreflect.hpp */ From c12a3ec726d04a311ec39cd84bc9fd4210438f5b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:13:27 -0400 Subject: [PATCH 0159/2524] + .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6abfb0de --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# lsp keeps state here +.cache +# typical build directory +build From a467f151bcaa9e6e3a1b6de1b2bbac279d9b19a6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:41:16 -0400 Subject: [PATCH 0160/2524] .gitignore: + compile_commands.json --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 6abfb0de..f52f1311 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .cache # typical build directory build +# lsp: symlink to file in build directory (established manually) +compile_commands.json From 415c81b1814140b0cf1774a846f16f6f2cf59ed3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:41:26 -0400 Subject: [PATCH 0161/2524] README: formatting + example --- README.md | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c020ad6e..b0500c48 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,52 @@ # python bindings for c++ reflection library (xo-reflect) -### build + install +## build + install ``` $ cd xo-pyreflect $ mkdir build $ cd build $ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local -$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. $ make $ make install ``` -### build for unit test coverage +## build for unit test coverage ``` $ cd xo-pyreflect $ mkdir build-ccov $ cd build-ccov -$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. ``` -### LSP support +## LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + ``` $ cd xo-pyreflect -$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +$ ln -s build/compile_commands.json # supply compile commands to LSP ``` + +## Examples + +Assumes `xo-pyreflect` installed to `~/local2/lib` + +``` +PYTHONPATH=~/local2/lib python +>>> import pyreflect +>>> dir(pyreflect) +['SelfTagging', 'TaggedRcptr', 'TypeDescr', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] +>>> pyreflect.TypeDescr.print_reflected_types() + +``` + +Not /immediately/ interesting: no reflected types in `pyreflect` itself From cd6bd92e3f6393fdb9512d9af4a370e58cabe507 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:52:45 -0400 Subject: [PATCH 0162/2524] xo-cmake: tweak exported header dirs --- cmake/xo_cxx.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 37867d59..9b14025f 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -60,11 +60,11 @@ macro(xo_include_headeronly_options2 target) # target_include_directories( ${target} INTERFACE - $ # e.g. for #include "indentlog/scope.hpp" $ + $ + $ # e.g. for #include "indentlog/scope.hpp" $ # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED] $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect -# $ $ # e.g. for generated .hpp files ) From bfc7828b0ab53fe8342992980699f5cff435b903 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:53:04 -0400 Subject: [PATCH 0163/2524] xo-cmake: bugfix: PUBLIC not INTERFACE in xo_dependency_headeronly() --- cmake/xo_cxx.cmake | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 9b14025f..5c4c3d6a 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -234,7 +234,12 @@ endmacro() # macro(xo_dependency_headeronly target dep) find_package(${dep} CONFIG REQUIRED) - target_link_libraries(${target} INTERFACE ${dep}) + # PUBLIC here is important -- it's needed so that include directories that are required by ${dep}, + # will be included in compilation of ${target}. + # + # INTERFACE doesn't make this happen; for a header-only library, it should be supplied to the add_library() macro + # + target_link_libraries(${target} PUBLIC ${dep}) endmacro() # dependency on namespaced target From 8247f9b56f77ee4519d54dab6934d2195d8d2b00 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:53:28 -0400 Subject: [PATCH 0164/2524] pybind11: hack to set library directory (won't work with nix) --- cmake/xo_cxx.cmake | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 5c4c3d6a..3ee07ddb 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -284,8 +284,24 @@ macro(xo_pybind11_link_flags) set_property( TARGET pybind11::python_link_helper APPEND - PROPERTY INTERFACE_LINK_OPTIONS "$<$:LINKER:-flat_namespace>" - ) + PROPERTY + INTERFACE_LINK_OPTIONS "$<$:LINKER:-flat_namespace>") + + # looks like pybind11_add_module() tries to link transitive deps + # of libs mentioned in xo_dependency() -- perhaps for link-time optimization? + # + # For example when linking libpyreflect, get link line with -lrefcnt + # (presumably since cmake knows that libreflect.so depends on librefcnt.so); + # this triggers error, since link doesn't know where to find librefcnt.so + # + # To workaround, add ${CMAKE_INSTALL_PREFIX}/lib to the link line. + # + # WARNING: expect this not to work in a hermetic nix build! + # + set_property(TARGET pybind11::python_link_helper + APPEND + PROPERTY + INTERFACE_LINK_OPTIONS "-L${CMAKE_INSTALL_PREFIX}/lib") endmacro() # ---------------------------------------------------------------- From cb74a35334240b31d48afd29cf85afdb4191668b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:53:56 -0400 Subject: [PATCH 0165/2524] xo-cmake: pybind11 install tweaks --- cmake/xo_cxx.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 3ee07ddb..1c323823 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -322,8 +322,13 @@ macro(xo_pybind11_library target source_files) # find_package(pybind11) + + # this only works if one source file, right? pybind11_add_module(${target} MODULE ${source_files}) + xo_pybind11_link_flags() + xo_include_options2(${target}) # use xo_install_library2() instead #install(TARGETS ${target} DESTINATION lib) + xo_install_library2(${target}) endmacro() From 7d1e2f39029b400cafa91a1eda62ce9525eb877a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:54:42 -0400 Subject: [PATCH 0166/2524] drop cmake-config-mediated indentlog dep (will see if need later) --- cmake/reflectConfig.cmake.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/reflectConfig.cmake.in b/cmake/reflectConfig.cmake.in index 9c15f36a..e98de9c1 100644 --- a/cmake/reflectConfig.cmake.in +++ b/cmake/reflectConfig.cmake.in @@ -1,4 +1,6 @@ @PACKAGE_INIT@ +#include(CMakeFindDependencyMacro) +#find_dependency(indentlog) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From 0c73b5417b908697629be986ba45513c1e832646 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:55:13 -0400 Subject: [PATCH 0167/2524] reflect: tidy indentlog dep --- src/reflect/CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index 6056ebca..f8d84446 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -6,10 +6,11 @@ set(SELF_SOURCE_FILES TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp atomic/At xo_add_shared_library(${SELF_LIBRARY_NAME} ${PROJECT_VERSION} 1 ${SELF_SOURCE_FILES}) # ---------------------------------------------------------------- -# dependencies: logutil, ... +# dependencies: indentlog, ... -xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) -xo_internal_dependency(${SELF_LIBRARY_NAME} refcnt) +# note: changes here must coordinate with cmake/reflectConfig.cmake.in +xo_dependency_headeronly(${SELF_LIBRARY_NAME} indentlog) +xo_dependency(${SELF_LIBRARY_NAME} refcnt) # ---------------------------------------------------------------- # 3rd party dependency: boost: From 5f22bca0dc92a1abe3754eadaaa47f50afe2e9e4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 6 Oct 2023 23:56:16 -0400 Subject: [PATCH 0168/2524] refcnt: build tweaks --- src/CMakeLists.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f8b65942..8a05a143 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,5 @@ -set(SELF_LIBRARY_NAME refcnt) -set(SELF_SOURCE_FILES Refcounted.cpp Displayable.cpp) - -xo_add_shared_library(${SELF_LIBRARY_NAME} ${PROJECT_VERSION} 1 ${SELF_SOURCE_FILES}) -xo_internal_dependency(${SELF_LIBRARY_NAME} indentlog) +set(SELF_LIB refcnt) +set(SELF_SRCS Refcounted.cpp Displayable.cpp) +xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_dependency_headeronly(${SELF_LIB} indentlog) From f25389cda2da75ba225d1a663d30dea9660af31b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 7 Oct 2023 00:02:05 -0400 Subject: [PATCH 0169/2524] initial commit --- CMakeLists.txt | 67 ++++++++++++++++++++++++++++++++++ README.md | 13 +++++++ cmake/xo_pyutilConfig.cmake.in | 4 ++ include/xo/pyutil/pyutil.hpp | 30 +++++++++++++++ 4 files changed, 114 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/xo_pyutilConfig.cmake.in create mode 100644 include/xo/pyutil/pyutil.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..ea3f4237 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,67 @@ +# pyutil/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pyutil VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see github:Rconybea/xo-cmake) +include(xo_macros/xo_cxx) +#include(xo_macros/code-coverage) # very little to unit test here + +# ---------------------------------------------------------------- +# unit test setup + +#enable_testing() +## enable code coverage for all executables+libraries +## (when configured with -DCODE_COVERAGE=ON) +## +#add_code_coverage() +#add_code_coverage_all_targets( +# EXCLUDE +# /nix/store/* +# ${PROJECT_SOURCE_DIR}/utest/*) + +# ---------------------------------------------------------------- +# bespoke (usually temporary) c++ settings + +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- +# c++ settings + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# external dependencies +# +# set CMAKE_INSTALL_PREFIX to analog of /usr +# to use .cmake assistants from /usr/lib/cmake/indentlog +# +# xo_dependency(..) + +# ---------------------------------------------------------------- + +#add_subdirectory(example) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# output targets + +set(SELF_LIB xo_pyutil) +add_library(${SELF_LIB} INTERFACE) +xo_include_headeronly_options2(${SELF_LIB}) + +# ---------------------------------------------------------------- +# standard install + provide find_package() support + +xo_install_library2(${SELF_LIB}) +xo_install_include_tree() +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install any additional components + +#install(TARGETS ex1 DESTINATION bin/${PROJECT_NAME}/example) diff --git a/README.md b/README.md new file mode 100644 index 00000000..d9bbae39 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# pybind11 utilities for XO projects + +# to build + install locally + +``` +$ cd xo-pyutil +$ mkdir build +$ cd build +$ PREFIX=/usr/local # for example +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=$(PREFIX) -DCMAKE_INSTALL_PREFIX=${PREFIX} .. +$ make +$ make install +``` diff --git a/cmake/xo_pyutilConfig.cmake.in b/cmake/xo_pyutilConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pyutilConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/pyutil/pyutil.hpp b/include/xo/pyutil/pyutil.hpp new file mode 100644 index 00000000..a450f5d2 --- /dev/null +++ b/include/xo/pyutil/pyutil.hpp @@ -0,0 +1,30 @@ +/* @file pyutil.hpp + * + * utility stuff to be used across multiple pybind11 .cpp files + */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include "xo/refcnt/Unowned.hpp" +#include + +/* xo::ref::intrusive_ptr is an intrusively-reference-counted pointer. + * always safe to create one from a T* p + * (since refcount is directly accessible from p) + * + * Need declaration like this before any pybind11 bindings + * that expose an object of types like + * (a) intrusive_ptr or + * (b) T * / T const * / T & / T const & to python. + * If this were not done, pybind11 would by default use unique_ptr> + * (ok but inefficient) or unique_ptr (fatal!) + */ +PYBIND11_DECLARE_HOLDER_TYPE(T, xo::ref::intrusive_ptr, true); + +/* xo::ref::unowned_ptr is an unmanaged pointer. + * use this for immortal objects that pybind11 must not delete. + */ +PYBIND11_DECLARE_HOLDER_TYPE(T, xo::ref::unowned_ptr, true); + +/* end pyutil.hpp */ From 863f87db7df9802516ff550f3d5c3d29c15dfd16 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 7 Oct 2023 00:16:07 -0400 Subject: [PATCH 0170/2524] xo-cmake: unwind mistake, revert to INTERFACE for headeronly dep --- cmake/xo_cxx.cmake | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 1c323823..150c665f 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -234,12 +234,17 @@ endmacro() # macro(xo_dependency_headeronly target dep) find_package(${dep} CONFIG REQUIRED) - # PUBLIC here is important -- it's needed so that include directories that are required by ${dep}, - # will be included in compilation of ${target}. + # Conflict here between PUBLIC and INTERFACE # - # INTERFACE doesn't make this happen; for a header-only library, it should be supplied to the add_library() macro + # PUBLIC ensures that include directories required by ${dep} will also be included in compilation of ${target}; + # we generally want this # - target_link_libraries(${target} PUBLIC ${dep}) + # INTERFACE mandatory when depending on a header-only library (created with add_library(foo INTERFACE)). + # otherwise get error: + # INTERFACE library can only be used with the INTERFACE keyword of + # target_link_libraries + # + target_link_libraries(${target} INTERFACE ${dep}) endmacro() # dependency on namespaced target From 0bc65c87271d91a83781599e20283355ff005b37 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 7 Oct 2023 00:31:35 -0400 Subject: [PATCH 0171/2524] experiment: try attaching dirs with PUBLIC keyword --- cmake/xo_cxx.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 150c665f..740e0833 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -59,7 +59,7 @@ macro(xo_include_headeronly_options2 target) # compiler's include path # target_include_directories( - ${target} INTERFACE + ${target} PUBLIC $ $ $ # e.g. for #include "indentlog/scope.hpp" From 7e80de41aa4f766fd3e2b0f6694f6fb1d9740f80 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 7 Oct 2023 00:34:56 -0400 Subject: [PATCH 0172/2524] revert failed experiment --- cmake/xo_cxx.cmake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 740e0833..e82d21d1 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -58,8 +58,11 @@ macro(xo_include_headeronly_options2 target) # need that build directory to also appear in # compiler's include path # + # NOTE: using INTERFACE here is mandatory. Otherwise get error: + # target_include_directories may only set INTERFACE properties on INTERFACE targets + # target_include_directories( - ${target} PUBLIC + ${target} INTERFACE $ $ $ # e.g. for #include "indentlog/scope.hpp" From 5ad75dec28c6b6bedbbd9ddb9be953a3688271a4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 7 Oct 2023 00:45:02 -0400 Subject: [PATCH 0173/2524] bugfix: workaround apparent cmake bug --- cmake/xo_cxx.cmake | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index e82d21d1..7be27f62 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -240,14 +240,23 @@ macro(xo_dependency_headeronly target dep) # Conflict here between PUBLIC and INTERFACE # # PUBLIC ensures that include directories required by ${dep} will also be included in compilation of ${target}; - # we generally want this + # i.e. will appear in property ${target}.INCLUDE_DIRECTORIES # # INTERFACE mandatory when depending on a header-only library (created with add_library(foo INTERFACE)). # otherwise get error: # INTERFACE library can only be used with the INTERFACE keyword of # target_link_libraries + # Unfortunately target_link_libraries() does not copy dependent's INTERFACE_INCLUDE_DIRECTORIES property + # (at least asof cmake 3.25.3). Dependent's INCLUDE_DIRECTORIES property will be empty, since it's header-only. + # + # Workaround by copying property explicity, which we do below # target_link_libraries(${target} INTERFACE ${dep}) + + get_target_property(xo_dependency_headeronly__tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) + set_property( + TARGET ${target} + APPEND PROPERTY INCLUDE_DIRECTORIES ${xo_dependency_headeronly__tmp}) endmacro() # dependency on namespaced target From ac5534d8e2717e4241b9e66ba4ef42dcf373ce15 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 7 Oct 2023 16:03:39 -0400 Subject: [PATCH 0174/2524] build: use new xo_pybind11_dependency() --- README.md | 2 +- src/pyreflect/CMakeLists.txt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b0500c48..434a80a5 100644 --- a/README.md +++ b/README.md @@ -49,4 +49,4 @@ PYTHONPATH=~/local2/lib python ``` -Not /immediately/ interesting: no reflected types in `pyreflect` itself +Not _immediately_ interesting: no reflected types in `pyreflect` itself diff --git a/src/pyreflect/CMakeLists.txt b/src/pyreflect/CMakeLists.txt index 9b63978f..0579d761 100644 --- a/src/pyreflect/CMakeLists.txt +++ b/src/pyreflect/CMakeLists.txt @@ -8,4 +8,5 @@ set(SELF_SRCS pyreflect.cpp) xo_pybind11_library(${SELF_LIB} ${SELF_SRCS}) -xo_dependency(${SELF_LIB} reflect) +xo_pybind11_dependency(${SELF_LIB} reflect) + From 2ccb974fbfec85b28e4efe423db77b9bd935170f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 7 Oct 2023 16:07:06 -0400 Subject: [PATCH 0175/2524] xo-cmake: + xo_pybind11_dependency() --- cmake/xo_cxx.cmake | 53 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 7be27f62..09a2fb22 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -341,11 +341,60 @@ macro(xo_pybind11_library target source_files) find_package(pybind11) # this only works if one source file, right? + # + # 6oct2023 + # Having trouble at link time with this. + # Getting broken link for nix, because link line lists short library nicknames + # (e.g. -lfoo) for transitive deps. nix link needs to be given the directory + # in which libfoo.so resides, so we need to ensure full path + # + # - source files: + # -fPIC -fvisibility=hidden -flto -fno-fat-lto-objects + # - library: + # -fPIC -flto + # (transitive closure of library deps for lto?) + # pybind11_add_module(${target} MODULE ${source_files}) xo_pybind11_link_flags() xo_include_options2(${target}) - # use xo_install_library2() instead - #install(TARGETS ${target} DESTINATION lib) xo_install_library2(${target}) endmacro() + +# ---------------------------------------------------------------- +# use this for a dependency of a pybind11 library, +# e.g. that was introduced by xo_pybind11_library() +# +# Working around the following problem (cmake 3.25.3, pybind11 2.10.4).N +# if: +# 1. we have pybind11 library pyfoo, depending on c++ native library foo. +# 2. foo depends on other libraries foodep1, foodep2; +# assume also that foodep2 is header-only +# +# if we write: +# # CMakeLists.txt +# pybind11_add_module(pyfoo MODULE pyfoo.cpp) +# find_package(foo CONFIG_REQUIRED) +# target_link_libraries(pyfoo PUBLIC foo) +# +# get compile instructions like: +# g++ -o pyfoo.cpython-311-x86_64-linux-gnu.so path/to/pyfoo.cpp.o path/to/libfoo.so.x.y -lfoodep1 -lfoodep2 +# +# 1. This is broken for foodep2, since there no libfoodep2.so exists +# 2. Also broken for nix build, because directory containing libfoodep1.so doesn't appear on the compile line. +# (It's likely possible to extract this from the .cmake package in lib/cmake/foo/fooTargets.cmake, +# but I don't know how to do that yet) +# +# workaround here is to suppress these secondary dependencies. +# This assumes: +# 1. secondary dependencies are all in shared libraries (not needed on link line) +# 2. (maybe?) primary dependency libfoo.so is sufficient to satisfy g++ +# -- conceivably true if libfoo.so has RUNPATH etc. +# +macro(xo_pybind11_dependency target dep) + find_package(${dep} CONFIG REQUIRED) + # clobber secondary dependencies, as discussed above + set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") + # now that secondary deps are gone, attach to target pybind11 library + xo_dependency(${target} ${dep}) +endmacro() From 46ff8f0b41d45d92a49d3f69abb5dba8cc27c008 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 13:50:08 -0400 Subject: [PATCH 0176/2524] cmake: + xo_hreaderonly_dependency() to fix confusion --- cmake/xo_cxx.cmake | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 09a2fb22..f0e2e29b 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -229,13 +229,9 @@ macro(xo_dependency target dep) target_link_libraries(${target} PUBLIC ${dep}) endmacro() -macro(xo_internal_dependency target dep) - xo_dependency(${target} ${dep}) -endmacro() - -# dependency on a header-only library +# dependency of a header-only library on another header-only library # -macro(xo_dependency_headeronly target dep) +macro(xo_headeronly_dependency target dep) find_package(${dep} CONFIG REQUIRED) # Conflict here between PUBLIC and INTERFACE # @@ -253,10 +249,10 @@ macro(xo_dependency_headeronly target dep) # target_link_libraries(${target} INTERFACE ${dep}) - get_target_property(xo_dependency_headeronly__tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) - set_property( - TARGET ${target} - APPEND PROPERTY INCLUDE_DIRECTORIES ${xo_dependency_headeronly__tmp}) +# get_target_property(xo_dependency_headeronly__tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) +# set_property( +# TARGET ${target} +# APPEND PROPERTY INCLUDE_DIRECTORIES ${xo_dependency_headeronly__tmp}) endmacro() # dependency on namespaced target @@ -396,5 +392,6 @@ macro(xo_pybind11_dependency target dep) # clobber secondary dependencies, as discussed above set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") # now that secondary deps are gone, attach to target pybind11 library - xo_dependency(${target} ${dep}) + # skip xo_dependency() here, that would repeat the find_package() expansion + target_link_libraries(${target} PUBLIC ${dep}) endmacro() From 50cf27b6a21b254f1fbdc92bf34537c0aa5d119b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 13:51:14 -0400 Subject: [PATCH 0177/2524] bugfix: xo/ in include path --- include/xo/subsys/Subsystem.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/subsys/Subsystem.hpp b/include/xo/subsys/Subsystem.hpp index b9a41e18..ece72db6 100644 --- a/include/xo/subsys/Subsystem.hpp +++ b/include/xo/subsys/Subsystem.hpp @@ -5,7 +5,7 @@ #pragma once -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" #include #include #include From 5019c9402474883a71f77740df6fc979d057c397 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 13:51:39 -0400 Subject: [PATCH 0178/2524] build nit: xo_internal_dependency() -> xo_dependency() --- CMakeLists.txt | 2 +- example/ex2/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 87383904..c5413d7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,7 @@ xo_toplevel_compile_options() # set CMAKE_INSTALL_PREFIX to analog of /usr # to use .cmake assistants from /usr/lib/cmake/indentlog # -# xo_internal_dependency(..) +# xo_dependency(..) # ---------------------------------------------------------------- diff --git a/example/ex2/CMakeLists.txt b/example/ex2/CMakeLists.txt index ee6687f2..78016d45 100644 --- a/example/ex2/CMakeLists.txt +++ b/example/ex2/CMakeLists.txt @@ -1,3 +1,3 @@ add_executable(ex2 ex2.cpp) xo_include_options2(ex2) -xo_internal_dependency(ex2 indentlog) +xo_dependency(ex2 indentlog) From f05068a45b2e15be7b438b44921caf47fe04873d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 13:52:02 -0400 Subject: [PATCH 0179/2524] bugfix: xo/ in include path --- include/xo/randomgen/print.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/randomgen/print.hpp b/include/xo/randomgen/print.hpp index e9860ab0..d5b0a992 100644 --- a/include/xo/randomgen/print.hpp +++ b/include/xo/randomgen/print.hpp @@ -2,6 +2,6 @@ #pragma once -#include "indentlog/print/array.hpp" +#include "xo/indentlog/print/array.hpp" /* end print.hpp */ From 19fe68dc0e873b0e6a5eb8e757fcca2b5d0becfe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 13:54:55 -0400 Subject: [PATCH 0180/2524] + .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..49f711e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# typical build directories +build +ccov From c494a44f990310bfe6034791c12cc9c5edef8cfe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 13:55:45 -0400 Subject: [PATCH 0181/2524] build: xo_dependency_headeronly() -> xo_dependency() --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8a05a143..9d35b21d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,4 +2,4 @@ set(SELF_LIB refcnt) set(SELF_SRCS Refcounted.cpp Displayable.cpp) xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) -xo_dependency_headeronly(${SELF_LIB} indentlog) +xo_dependency(${SELF_LIB} indentlog) From 58b7f567e9a5bf772c317171bfd4b73972841ceb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 13:56:20 -0400 Subject: [PATCH 0182/2524] build: xo_dependency_headeronly() -> xo_dependency() --- src/reflect/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index f8d84446..e22a2c4c 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -9,7 +9,7 @@ xo_add_shared_library(${SELF_LIBRARY_NAME} ${PROJECT_VERSION} 1 ${SELF_SOURCE_FI # dependencies: indentlog, ... # note: changes here must coordinate with cmake/reflectConfig.cmake.in -xo_dependency_headeronly(${SELF_LIBRARY_NAME} indentlog) +xo_dependency(${SELF_LIBRARY_NAME} indentlog) xo_dependency(${SELF_LIBRARY_NAME} refcnt) # ---------------------------------------------------------------- From 5da396c8c3f85811f1c7f078a3c1f20beb72e334 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 14:05:42 -0400 Subject: [PATCH 0183/2524] build: xo_dependency_headeronly() -> xo_headeronly_dependency() --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cde16d98..94bd08fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,4 +48,6 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- # input dependencies -xo_dependency_headeronly(${SELF_LIB} randomgen) +# xo-ordinaltree is also header-only +# +xo_headeronly_dependency(${SELF_LIB} randomgen) From e1bbb4261c21a2fcf4bc9c70dc76d8538cec7e61 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 14:10:38 -0400 Subject: [PATCH 0184/2524] buld: simplify: just need xo_dependency() --- utest/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index ea866722..26b370b4 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -13,9 +13,9 @@ target_code_coverage(${SELF_EXE} AUTO ALL) # ---------------------------------------------------------------- # internal dependencies: refcnt, ... -xo_internal_dependency(${SELF_EXE} refcnt) -xo_internal_dependency(${SELF_EXE} indentlog) -xo_dependency_headeronly(${SELF_EXE} randomgen) +xo_dependency(${SELF_EXE} refcnt) +xo_dependency(${SELF_EXE} indentlog) +xo_dependency(${SELF_EXE} randomgen) # ---------------------------------------------------------------- # 3rd part dependency: catch2: From 4daef0555000f41b8485a765ac85adfe216c9714 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 8 Oct 2023 14:21:40 -0400 Subject: [PATCH 0185/2524] build: + testing (even though no tests) for consistency --- CMakeLists.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ea3f4237..c7e32668 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,20 +7,20 @@ enable_language(CXX) # common XO cmake macros (see github:Rconybea/xo-cmake) include(xo_macros/xo_cxx) -#include(xo_macros/code-coverage) # very little to unit test here +include(xo_macros/code-coverage) # very little to unit test here # ---------------------------------------------------------------- # unit test setup -#enable_testing() +enable_testing() ## enable code coverage for all executables+libraries ## (when configured with -DCODE_COVERAGE=ON) ## -#add_code_coverage() -#add_code_coverage_all_targets( -# EXCLUDE -# /nix/store/* -# ${PROJECT_SOURCE_DIR}/utest/*) +add_code_coverage() +add_code_coverage_all_targets( + EXCLUDE + /nix/store/* + ${PROJECT_SOURCE_DIR}/utest/*) # ---------------------------------------------------------------- # bespoke (usually temporary) c++ settings From effbbf22d9f4238147274befbe1116a288cc535e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 13:49:08 -0400 Subject: [PATCH 0186/2524] initial implementation --- CMakeLists.txt | 52 ++++ README.md | 26 ++ cmake/printjsonConfig.cmake.in | 6 + include/xo/printjson/JsonPrinter.hpp | 99 ++++++ include/xo/printjson/PrintJson.hpp | 143 +++++++++ include/xo/printjson/init_printjson.hpp | 29 ++ src/printjson/CMakeLists.txt | 13 + src/printjson/PrintJson.cpp | 398 ++++++++++++++++++++++++ src/printjson/init_printjson.cpp | 32 ++ 9 files changed, 798 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/printjsonConfig.cmake.in create mode 100644 include/xo/printjson/JsonPrinter.hpp create mode 100644 include/xo/printjson/PrintJson.hpp create mode 100644 include/xo/printjson/init_printjson.hpp create mode 100644 src/printjson/CMakeLists.txt create mode 100644 src/printjson/PrintJson.cpp create mode 100644 src/printjson/init_printjson.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..028f4c5e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +# printjson/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(printjson VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# 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 + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(src/printjson) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support for printjson customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install .hpp files + +xo_install_include_tree() + +# end CMakeLists.txt diff --git a/README.md b/README.md new file mode 100644 index 00000000..ab049018 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# printjson library + +### build + install +``` +$ cd xo-printjson +$ mkdir build +$ cd build +$ PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} .. +$ make +$ make install +``` + +### build for unit test coverage +``` +$ cd xo-printjson +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP support +``` +$ cd xo-printjson +$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` diff --git a/cmake/printjsonConfig.cmake.in b/cmake/printjsonConfig.cmake.in new file mode 100644 index 00000000..c7d8974c --- /dev/null +++ b/cmake/printjsonConfig.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +#include(CMakeFindDependencyMacro) +#find_dependency(refcnt) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/printjson/JsonPrinter.hpp b/include/xo/printjson/JsonPrinter.hpp new file mode 100644 index 00000000..c1b8b62a --- /dev/null +++ b/include/xo/printjson/JsonPrinter.hpp @@ -0,0 +1,99 @@ +/* @file JsonPrinter.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "xo/reflect/Reflect.hpp" +#include "xo/reflect/TypeDrivenMap.hpp" +#include "xo/reflect/TaggedPtr.hpp" +//#include +#include + +namespace xo { + namespace json { + class PrintJson; + + class JsonPrinter { + public: + using Reflect = xo::reflect::Reflect; + using TaggedPtr = xo::reflect::TaggedPtr; + using TypeDescr = xo::reflect::TypeDescr; + using TypeId = xo::reflect::TypeId; + + public: + JsonPrinter(PrintJson const * pjson) : pjson_{pjson} {} + virtual ~JsonPrinter() = default; + + PrintJson const * pjson() const { return pjson_; } + + /* print tagged pointer in json format */ + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const = 0; + + void report_internal_type_consistency_error(TypeDescr td1, + TypeDescr td2, + std::ostream * p_os) const; + + /* convenience method for derived printers. + * retrieves contents of tp as a T*, complains to *p_os if that fails. + * + * (Failure would occur if printer for type T was instead installed + * for some unrelated type U) + */ + template + T * check_recover_native(TaggedPtr tp, std::ostream * p_os) const { + T * x = tp.recover_native(); + + if (!x) { + this->report_internal_type_consistency_error(Reflect::require(), + tp.td(), + p_os); + } + + return x; + } /*check_recover_native*/ + + void assign_pjson(PrintJson const * pjson) { + assert(this->pjson_ == nullptr || this->pjson_ == pjson); + this->pjson_ = pjson; + } /*assign_pjson*/ + + private: + /* a json printers is installed into one PrintJson instance; + * capture address of that instance at install time + */ + PrintJson const * pjson_ = nullptr; + }; /*JsonPrinter*/ + + /* AsStringJsonPrinter + * prints a T-instance by using operator<< and surrounding in quotes. + * + * e.g: + * T & x = ..; + * std::ostream * p_os = ..; + * + * *p_os << "\"" << x << "\"" + * + */ + template + class AsStringJsonPrinter : public JsonPrinter { + public: + AsStringJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override + { + T * x = this->check_recover_native(tp, p_os); + + if(x) { + *p_os << "\"" << *x << "\""; + } + } /*print_json*/ + }; /*AsStringJsonPrinter*/ + } /*namespace json*/ +} /*namespace xo*/ + + +/* end JsonPrinter.hpp */ diff --git a/include/xo/printjson/PrintJson.hpp b/include/xo/printjson/PrintJson.hpp new file mode 100644 index 00000000..c0ddcf55 --- /dev/null +++ b/include/xo/printjson/PrintJson.hpp @@ -0,0 +1,143 @@ +/* @file JsonPrinter.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "JsonPrinter.hpp" +#include "xo/reflect/TypeDrivenMap.hpp" +#include "xo/reflect/SelfTagging.hpp" +#include +#include + +namespace xo { + namespace json { + class PrintJson : public reflect::SelfTagging { + public: + using Reflect = xo::reflect::Reflect; + using TypeDrivenMap = xo::reflect::TypeDrivenMap>; + using SelfTagging = xo::reflect::SelfTagging; + using TaggedPtr = xo::reflect::TaggedPtr; + using TaggedRcptr = xo::reflect::TaggedRcptr; + using TypeDescr = xo::reflect::TypeDescr; + using TypeId = xo::reflect::TypeId; + + public: + PrintJson(); + ~PrintJson() = default; + + template + void print(T const & x_arg, std::ostream * p_os) const { + T * x = const_cast(&x_arg); + + this->print_tp(Reflect::make_tp(x), p_os); + } /*print*/ + + /* print object tp on stream *p_os, in JSON format; + */ + void print_tp(TaggedPtr tp, std::ostream * p_os) const; + + /* convenience -- shorthand for + * .print(obj->self_tp(), p_os) + */ + void print_obj(ref::rp const & obj, std::ostream * p_os) const; + + void provide_printer(TypeId id, std::unique_ptr p) { + *(printer_map_.require(id)) = std::move(p); + } + + void provide_printer(TypeDescr td, std::unique_ptr p) { + this->provide_printer(td->id(), std::move(p)); + } + + /* write json representation for tp on *p_os */ + void print_aux(TaggedPtr tp, std::ostream * p_os) const; + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp(); + + private: + /* provide printers for common basic types */ + void provide_std_printers(); + + private: + /* map contains specialized printers for specific c++ types */ + TypeDrivenMap printer_map_; + }; /*PrintJson*/ + + /* Using singleton here to collect type-specific json printers, + * collected during program initialization. + * + * Could relabel as PrintJsonInitContext if desired + */ + class PrintJsonSingleton { + public: + static ref::rp instance(); + + private: + /* we don't need this to be stored as pointer. + * memory burned if unused will be one empty std::vector<> + */ + static ref::rp s_instance; + }; /*PrintJsonSingleton*/ + + } /*namespace json*/ + +#ifdef NOT_USING + namespace print { + using PrintJson = xo::json::PrintJson; + + /* stream inserter for printing a T-instance in json format */ + template + class jsonp_impl { + public: + jsonp_impl(T const & x, PrintJson const * pjson) : value_(x), pjson_{pjson} {} + //jsonp_impl(T const & x, PrintJson const * pjson) : value_{x}, pjson_{pjson} {} + //jsonp_impl(T && x, PrintJson const * pjson) : value_(std::move(x)), pjson_{pjson} {} + + void print(std::ostream & os) const { + using xo::reflect::Reflect; + + this->pjson_->print_tp(Reflect::make_tp(&value_), &os); + } /*print*/ + + private: + /* value, to be printed, in json format */ + T value_; + /* json printer (bc we don't care for singletons) */ + PrintJson const * pjson_ = nullptr; + }; /*jsonp_impl*/ + + template + inline + std::ostream & operator<<(std::ostream & os, jsonp_impl const & x) { + x.print(os); + return os; + } /*operator<<*/ + + /* writing out std::forward behavior for completeness' sake: + * + * 1. call jsonp(x) with rvalue std::string x, then: + * - T will be deduced to [std::string] + * (in particular: _not_ std::string &, std::string const &, std::string &&) + * - rvalue std::string passed to jsonp_impl ctor + * + * 2a. call jsonp(x) with std::string & x, then: + * - T deduced to [std::string &] + * - std::string & passed to jsonp_impl ctor + * + * 2b. call jsonp(x) with std::string const & x, then: + * - T deduced to [std::string const &] + * - std::string const & passed to jsonp_impl ctor + */ + template + auto jsonp(T && x, PrintJson const * pjson) { + return jsonp_impl(std::forward(x), pjson); + } /*jsonp*/ + } /*namespace print*/ +#endif +} /*namespace xo*/ + +/* end JsonPrinter.hpp */ diff --git a/include/xo/printjson/init_printjson.hpp b/include/xo/printjson/init_printjson.hpp new file mode 100644 index 00000000..f2b06a19 --- /dev/null +++ b/include/xo/printjson/init_printjson.hpp @@ -0,0 +1,29 @@ +/* file init_printjson.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + /* tag to represent the printjson/ subsystem within ordered initialization */ + enum S_printjson_tag {}; + + /* Use: + * // anywhere, to declare printjson dependency e.g. at file scope + * InitEvidence s_evidence = InitSubsys::require(); + * + * // from main(), though can resort to module initialization in a pybind11 library + * Subsystem::initialize_all(); + */ + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + + +/* end init_printjson.hpp */ diff --git a/src/printjson/CMakeLists.txt b/src/printjson/CMakeLists.txt new file mode 100644 index 00000000..cf191d02 --- /dev/null +++ b/src/printjson/CMakeLists.txt @@ -0,0 +1,13 @@ +# xo-printjson/src/printjson/CMakeLists.txt + +set(SELF_LIB printjson) +set(SELF_SRCS PrintJson.cpp init_printjson.cpp) + +xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# dependencies: indentlog, ... + +xo_dependency(${SELF_LIB} reflect) + +# end CMakeLists.txt diff --git a/src/printjson/PrintJson.cpp b/src/printjson/PrintJson.cpp new file mode 100644 index 00000000..1ac048b6 --- /dev/null +++ b/src/printjson/PrintJson.cpp @@ -0,0 +1,398 @@ +/* file JsonPrinter.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "PrintJson.hpp" +//#include "time/Time.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include "xo/indentlog/print/tag.hpp" +#include + +namespace xo { + using xo::time::utc_nanos; + using xo::reflect::Metatype; + using xo::reflect::Reflect; + using xo::reflect::SelfTagging; + using xo::reflect::TypeDescr; + using xo::reflect::TaggedPtr; + using xo::reflect::TaggedRcptr; + using xo::time::iso8601; + using xo::ref::rp; + using xo::xtag; + + namespace json { + TaggedRcptr + PrintJson::self_tp() + { + return Reflect::make_rctp(this); + //return TaggedRcptr::make(this); + } /*self_tp*/ + + void + JsonPrinter::report_internal_type_consistency_error(TypeDescr td1, + TypeDescr td2, + std::ostream * p_os) const + { + *p_os << "canonical_name()) + << xtag("S", td2->canonical_name()) + << ">"; + } /*report_internal_type_consistency_error*/ + + namespace { + /* this will be used when TaggedPtr refers to a pointer-like value, + * e.g. + * xo::ref::rp + */ + void + print_generic_pointer(PrintJson const & print_json, + TaggedPtr tp, + std::ostream * p_os) + { + /* e.g. if + * xo::ref::rp opt = ...; + * then expect to print just as we would for + * VanillaOption & opt = ...; + * if pointer is null, will print {} + */ + + if (tp.n_child()) { + print_json.print_aux(tp.get_child(0), p_os); + } else { + /* note: this can be distinguished from a bona fide struct, + * b/c it doesn't supply the _name_ member + */ + *p_os << "{}"; + } + } /*print_generic_pointer*/ + + /* this will be used when TaggedPtr refers to a vector-like value, + * e.g. + * std::vector + * std::array + */ + void + print_generic_vector(PrintJson const & print_json, + TaggedPtr tp, + std::ostream * p_os) + { + /* e.g. if + * std::array v{1, 2, 3}; + * + * then expect to print + * [1.0, 2.0, 3.0] + */ + + *p_os << "["; + + for (uint32_t i = 0, n = tp.n_child(); i < n; ++i) { + if (i > 0) + *p_os << ", "; + + print_json.print_aux(tp.get_child(i), p_os); + } + + *p_os << "]"; + } /*print_generic_vector*/ + + /* this will be used when TaggedPtr is understood to refer to a struct-like value. + */ + void + print_generic_struct(PrintJson const & print_json, + TaggedPtr tp, + std::ostream * p_os) + { + /* e.g. if + * struct Foo { int x_; double y_; }; + * Foo foo{1, 1.4142}; + * + * then expect to print + * {"_name_": "Foo", "x": 1, "y": 1.4142} + * + * note that python json parser requires property names in double quotes + */ + + *p_os << "{"; + + *p_os << "\"_name_\": \"" << tp.td()->short_name() << "\""; + + for (uint32_t i = 0, n = tp.n_child(); i < n; ++i) { + *p_os << ", \"" << tp.struct_member_name(i) << "\": "; + + print_json.print_aux(tp.get_child(i), p_os); + } + + *p_os << "}"; + } /*print_generic_struct*/ + + } /*namespace*/ + + void + PrintJson::print_aux(TaggedPtr tp, + std::ostream * p_os) const + { + if (tp.td()) { + TypeId id = tp.td()->id(); + + std::unique_ptr const * printer + = this->printer_map_.lookup(id); + + if (printer && *printer) { + (*printer)->print_json(tp, p_os); + } else { + /* if no special-case printer, apply generic printing behavior */ + switch (tp.td()->metatype()) { + case Metatype::mt_pointer: + print_generic_pointer(*this, tp, p_os); + return; + case Metatype::mt_vector: + print_generic_vector(*this, tp, p_os); + return; + case Metatype::mt_struct: + print_generic_struct(*this, tp, p_os); + return; + case Metatype::mt_invalid: + case Metatype::mt_atomic: + break; + } + + (*p_os) << "canonical_name()) + << xtag("metatype", tp.td()->metatype()) + << ">"; + } + } else { + (*p_os) << ""; + } + } /*print_aux*/ + + void + PrintJson::print_tp(TaggedPtr tp, + std::ostream * p_os) const + { + this->print_aux(tp, p_os); + //*p_os << std::ends; + } /*print*/ + + void + PrintJson::print_obj(rp const & obj, std::ostream * p_os) const + { + assert(obj.get()); + + this->print_tp(obj->self_tp(), p_os); + } /*print_obj*/ + + class JsonPrinter_bool : public JsonPrinter { + public: + JsonPrinter_bool(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override { + bool * x = this->check_recover_native(tp, p_os); + + if (x) { + /* json boolean format is lower case true/false. + * (note that this conflicts with python True/False, achtung!) + */ + *p_os << (*x ? "true" : "false"); + } + } /*print_json*/ + }; /*JsonPrinter_bool*/ + + namespace { + void + provide_bool_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_bool(p_json)); + + p_json->provide_printer(Reflect::require(), + std::move(printer)); + } /*provide_bool_printer*/ + } /*namespace*/ + + template + class JsonPrinter_integer : public JsonPrinter { + public: + JsonPrinter_integer(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override { + T * x = tp.recover_native(); + + if (x) { + *p_os << *x; + } else { + report_internal_type_consistency_error(Reflect::require(), + tp.td(), + p_os); + } + } /*print_json*/ + }; /*JsonPrinter_integer*/ + + namespace { + template + void + provide_integer_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_integer(p_json)); + + p_json->provide_printer(Reflect::require(), std::move(printer)); + } /*provide_integer_printer*/ + } + + template + class JsonPrinter_floatingpoint : public JsonPrinter { + public: + JsonPrinter_floatingpoint(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override + { + T * x = tp.recover_native(); + + if (x) { + if (std::isfinite(*x)) { + *p_os << *x; + } else { + /* special cases. + * use javascript-friendly format + * + * Note non-finite floating-point values are not representable in + * standard json (?!#), though it's a standard extension + */ + + if (std::isnan(*x)) + *p_os << "NaN"; + else if (*x > 0.0) + *p_os << "Infinity"; + else + *p_os << "-Infinity"; + } + } else { + report_internal_type_consistency_error(Reflect::require(), + tp.td(), + p_os); + } + } /*print_json*/ + }; /*JsonPrinter_floatingpoint*/ + + namespace { + template + void + provide_floatingpoint_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_floatingpoint(p_json)); + + p_json->provide_printer(Reflect::require(), std::move(printer)); + } /*provide_floatingpoint_printer*/ + } /*namespace*/ + + template + class JsonPrinter_string : public JsonPrinter { + public: + JsonPrinter_string(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override { + T * x = tp.recover_native(); + + if (x) { + /* TODO: escapes special characters */ + *p_os << *x; + } else { + report_internal_type_consistency_error(Reflect::require(), + tp.td(), + p_os); + } + } /*print_json*/ + }; /*JsonPrinter_string*/ + + namespace { + template + void + provide_string_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_string(p_json)); + + p_json->provide_printer(Reflect::require(), std::move(printer)); + } /*provide_string_printer*/ + } /*namespace */ + + class JsonPrinter_utc_nanos : public JsonPrinter { + public: + JsonPrinter_utc_nanos(PrintJson * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override { + utc_nanos * x = tp.recover_native(); + + if (x) { + /* format like + * "2012-04-23T18:25:43.511Z" + * since that's what javascript uses + */ + *p_os << "\"" << iso8601(*x) << "\""; + } else { + report_internal_type_consistency_error(Reflect::require(), + tp.td(), + p_os); + } + } /*print_json*/ + }; /*JsonPrinter_utc_nanos*/ + + namespace { + void + provide_utc_nanos_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_utc_nanos(p_json)); + + p_json->provide_printer(Reflect::require(), + std::move(printer)); + } /*provide_utc_nanos_printer*/ + } /*namespace*/ + + PrintJson::PrintJson() { + this->provide_std_printers(); + } /*ctor*/ + + /* provide printers for common basic types */ + void + PrintJson::provide_std_printers() + { + provide_bool_printer(this); + + provide_integer_printer(this); + provide_integer_printer(this); + provide_integer_printer(this); + provide_integer_printer(this); + provide_integer_printer(this); + provide_integer_printer(this); + + provide_floatingpoint_printer(this); + provide_floatingpoint_printer(this); + + provide_string_printer(this); + provide_string_printer(this); + provide_string_printer(this); + provide_string_printer(this); + + provide_utc_nanos_printer(this); + } /*provide_std_printers*/ + + rp + PrintJsonSingleton::s_instance; + + rp + PrintJsonSingleton::instance() + { + if (!s_instance) + s_instance = new PrintJson(); + + return s_instance; + } /*instance*/ + + } /*namespace json*/ +} /*namespace xo*/ + +/* end JsonPrinter.cpp */ diff --git a/src/printjson/init_printjson.cpp b/src/printjson/init_printjson.cpp new file mode 100644 index 00000000..6bbaf01b --- /dev/null +++ b/src/printjson/init_printjson.cpp @@ -0,0 +1,32 @@ +/* file init_printjson.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "init_printjson.hpp" +#include "xo/reflect/init_reflect.hpp" +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + void + InitSubsys::init() + { + /* placeholder -- expecting there to be non-trivial content soon */ + } /*init*/ + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* subsystem dependencies for printjson/ */ + retval ^= InitSubsys::require(); + + /* printjson/'s own initialization code */ + retval ^= Subsystem::provide("printjson", &init); + + return retval; + } /*require*/ +} /*namespace xo*/ + +/* end init_printjson.cpp */ From 25380b8da7b83bd0ab7dbd379d67b703e0e00baa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 13:51:34 -0400 Subject: [PATCH 0187/2524] + .gitignore --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2323db6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# lsp keeps state here +.cache +# typical build directories +build +ccov +# compile_commands.json symlink -> build/compile_commands.json should be created manually +compile_commands.json From 5840dffe50e5b29022e9455f3068db8c058638e1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 13:55:12 -0400 Subject: [PATCH 0188/2524] github: + workflow (build on base ubuntu) --- .github/workflows/main.yml | 136 +++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..c11fc4ee --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,136 @@ +name: build on ubuntu base platform for xo-printjson + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Configure self (printjson) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (printjson) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Test self (printjson) + working-directory: ${{github.workspace}}/build_printjson + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 26dffce47026e740564cca6ac01ff23f23b6c3a2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:00:54 -0400 Subject: [PATCH 0189/2524] build: bugfix: support transitive deps in find_package helper --- cmake/reflectConfig.cmake.in | 6 ++++-- src/reflect/CMakeLists.txt | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cmake/reflectConfig.cmake.in b/cmake/reflectConfig.cmake.in index e98de9c1..ce449a35 100644 --- a/cmake/reflectConfig.cmake.in +++ b/cmake/reflectConfig.cmake.in @@ -1,6 +1,8 @@ @PACKAGE_INIT@ -#include(CMakeFindDependencyMacro) -#find_dependency(indentlog) +include(CMakeFindDependencyMacro) +find_dependency(refcnt) +find_dependency(indentlog) +find_dependency(subsys) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index e22a2c4c..27485780 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -8,9 +8,9 @@ xo_add_shared_library(${SELF_LIBRARY_NAME} ${PROJECT_VERSION} 1 ${SELF_SOURCE_FI # ---------------------------------------------------------------- # dependencies: indentlog, ... -# note: changes here must coordinate with cmake/reflectConfig.cmake.in -xo_dependency(${SELF_LIBRARY_NAME} indentlog) xo_dependency(${SELF_LIBRARY_NAME} refcnt) +xo_dependency(${SELF_LIBRARY_NAME} indentlog) +xo_dependency(${SELF_LIBRARY_NAME} subsys) # ---------------------------------------------------------------- # 3rd party dependency: boost: From d0ccff195f49712ded3bc015b6619ba8ef685f2d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:03:10 -0400 Subject: [PATCH 0190/2524] bugfix: include path + reindent --- include/xo/reflect/TypeDrivenMap.hpp | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/include/xo/reflect/TypeDrivenMap.hpp b/include/xo/reflect/TypeDrivenMap.hpp index c38c8739..3eb42660 100644 --- a/include/xo/reflect/TypeDrivenMap.hpp +++ b/include/xo/reflect/TypeDrivenMap.hpp @@ -5,42 +5,42 @@ #pragma once -#include "reflect/TypeDescr.hpp" +#include "TypeDescr.hpp" #include namespace xo { - namespace reflect { - /* represents a map :: TypeId -> Value */ - template - class TypeDrivenMap { - public: - Value const * lookup(TypeId id) const { return this->lookup_slot(id); } + namespace reflect { + /* represents a map :: TypeId -> Value */ + template + class TypeDrivenMap { + public: + Value const * lookup(TypeId id) const { return this->lookup_slot(id); } - Value * require(TypeId id) { return this->require_slot(id); } - Value * require(TypeDescr td) { return this->require_slot(td->id()); } - - private: - Value const * lookup_slot(TypeId id) const { - if (this->contents_v_.size() <= id.id()) - return nullptr; + Value * require(TypeId id) { return this->require_slot(id); } + Value * require(TypeDescr td) { return this->require_slot(td->id()); } - return &(this->contents_v_[id.id()]); - } /*lookup_slot*/ + private: + Value const * lookup_slot(TypeId id) const { + if (this->contents_v_.size() <= id.id()) + return nullptr; - 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()]); + } /*lookup_slot*/ - return &(this->contents_v_[id.id()]); - } /*require_slot*/ + Value * require_slot(TypeId id) { + if (this->contents_v_.size() <= id.id()) + this->contents_v_.resize(id.id() + 1); - private: - /* since TypeId/s are unique, compact sequence numbers, - * can efficiently store mapping to Values using a vector indexed by TypeId - */ - std::vector contents_v_; - }; /*TypeDrivenMap*/ - } /*namespace reflect*/ + return &(this->contents_v_[id.id()]); + } /*require_slot*/ + + private: + /* since TypeId/s are unique, compact sequence numbers, + * can efficiently store mapping to Values using a vector indexed by TypeId + */ + std::vector contents_v_; + }; /*TypeDrivenMap*/ + } /*namespace reflect*/ } /*namespace xo*/ From a228728445752cd907e4c3b6c82673424df449e9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:53:12 -0400 Subject: [PATCH 0191/2524] printjson: indentation in .hpp --- include/xo/printjson/JsonPrinter.hpp | 136 +++++++++++++-------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/include/xo/printjson/JsonPrinter.hpp b/include/xo/printjson/JsonPrinter.hpp index c1b8b62a..2be405c0 100644 --- a/include/xo/printjson/JsonPrinter.hpp +++ b/include/xo/printjson/JsonPrinter.hpp @@ -12,87 +12,87 @@ #include namespace xo { - namespace json { - class PrintJson; + namespace json { + class PrintJson; - class JsonPrinter { - public: - using Reflect = xo::reflect::Reflect; - using TaggedPtr = xo::reflect::TaggedPtr; - using TypeDescr = xo::reflect::TypeDescr; - using TypeId = xo::reflect::TypeId; + class JsonPrinter { + public: + using Reflect = xo::reflect::Reflect; + using TaggedPtr = xo::reflect::TaggedPtr; + using TypeDescr = xo::reflect::TypeDescr; + using TypeId = xo::reflect::TypeId; - public: - JsonPrinter(PrintJson const * pjson) : pjson_{pjson} {} - virtual ~JsonPrinter() = default; + public: + JsonPrinter(PrintJson const * pjson) : pjson_{pjson} {} + virtual ~JsonPrinter() = default; - PrintJson const * pjson() const { return pjson_; } + PrintJson const * pjson() const { return pjson_; } - /* print tagged pointer in json format */ - virtual void print_json(TaggedPtr tp, - std::ostream * p_os) const = 0; + /* print tagged pointer in json format */ + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const = 0; - void report_internal_type_consistency_error(TypeDescr td1, - TypeDescr td2, - std::ostream * p_os) const; + void report_internal_type_consistency_error(TypeDescr td1, + TypeDescr td2, + std::ostream * p_os) const; - /* convenience method for derived printers. - * retrieves contents of tp as a T*, complains to *p_os if that fails. - * - * (Failure would occur if printer for type T was instead installed - * for some unrelated type U) - */ - template - T * check_recover_native(TaggedPtr tp, std::ostream * p_os) const { - T * x = tp.recover_native(); + /* convenience method for derived printers. + * retrieves contents of tp as a T*, complains to *p_os if that fails. + * + * (Failure would occur if printer for type T was instead installed + * for some unrelated type U) + */ + template + T * check_recover_native(TaggedPtr tp, std::ostream * p_os) const { + T * x = tp.recover_native(); - if (!x) { - this->report_internal_type_consistency_error(Reflect::require(), - tp.td(), - p_os); - } + if (!x) { + this->report_internal_type_consistency_error(Reflect::require(), + tp.td(), + p_os); + } - return x; - } /*check_recover_native*/ + return x; + } /*check_recover_native*/ - void assign_pjson(PrintJson const * pjson) { - assert(this->pjson_ == nullptr || this->pjson_ == pjson); - this->pjson_ = pjson; - } /*assign_pjson*/ + void assign_pjson(PrintJson const * pjson) { + assert(this->pjson_ == nullptr || this->pjson_ == pjson); + this->pjson_ = pjson; + } /*assign_pjson*/ - private: - /* a json printers is installed into one PrintJson instance; - * capture address of that instance at install time - */ - PrintJson const * pjson_ = nullptr; - }; /*JsonPrinter*/ + private: + /* a json printers is installed into one PrintJson instance; + * capture address of that instance at install time + */ + PrintJson const * pjson_ = nullptr; + }; /*JsonPrinter*/ - /* AsStringJsonPrinter - * prints a T-instance by using operator<< and surrounding in quotes. - * - * e.g: - * T & x = ..; - * std::ostream * p_os = ..; - * - * *p_os << "\"" << x << "\"" - * - */ - template - class AsStringJsonPrinter : public JsonPrinter { - public: - AsStringJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} + /* AsStringJsonPrinter + * prints a T-instance by using operator<< and surrounding in quotes. + * + * e.g: + * T & x = ..; + * std::ostream * p_os = ..; + * + * *p_os << "\"" << x << "\"" + * + */ + template + class AsStringJsonPrinter : public JsonPrinter { + public: + AsStringJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} - virtual void print_json(TaggedPtr tp, - std::ostream * p_os) const override - { - T * x = this->check_recover_native(tp, p_os); + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override + { + T * x = this->check_recover_native(tp, p_os); - if(x) { - *p_os << "\"" << *x << "\""; - } - } /*print_json*/ - }; /*AsStringJsonPrinter*/ - } /*namespace json*/ + if(x) { + *p_os << "\"" << *x << "\""; + } + } /*print_json*/ + }; /*AsStringJsonPrinter*/ + } /*namespace json*/ } /*namespace xo*/ From 3b00641d17b79ef4ed2c8617cf7fe9aa4fc0608e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:53:51 -0400 Subject: [PATCH 0192/2524] init_printjson: always create singleton --- src/printjson/init_printjson.cpp | 34 ++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/printjson/init_printjson.cpp b/src/printjson/init_printjson.cpp index 6bbaf01b..893ffaf3 100644 --- a/src/printjson/init_printjson.cpp +++ b/src/printjson/init_printjson.cpp @@ -4,29 +4,33 @@ */ #include "init_printjson.hpp" +#include "PrintJson.hpp" #include "xo/reflect/init_reflect.hpp" #include "xo/subsys/Subsystem.hpp" namespace xo { - void - InitSubsys::init() - { - /* placeholder -- expecting there to be non-trivial content soon */ - } /*init*/ + using xo::json::PrintJsonSingleton; - InitEvidence - InitSubsys::require() - { - InitEvidence retval; + void + InitSubsys::init() + { + /* create singleton */ + PrintJsonSingleton::instance(); + } /*init*/ - /* subsystem dependencies for printjson/ */ - retval ^= InitSubsys::require(); + InitEvidence + InitSubsys::require() + { + InitEvidence retval; - /* printjson/'s own initialization code */ - retval ^= Subsystem::provide("printjson", &init); + /* subsystem dependencies for printjson/ */ + retval ^= InitSubsys::require(); - return retval; - } /*require*/ + /* printjson/'s own initialization code */ + retval ^= Subsystem::provide("printjson", &init); + + return retval; + } /*require*/ } /*namespace xo*/ /* end init_printjson.cpp */ From 0f4f874587b1530fe7d30b6247d6068322646db5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:54:17 -0400 Subject: [PATCH 0193/2524] printjson: indentation in .hpp --- include/xo/printjson/init_printjson.hpp | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/include/xo/printjson/init_printjson.hpp b/include/xo/printjson/init_printjson.hpp index f2b06a19..dbad03f8 100644 --- a/include/xo/printjson/init_printjson.hpp +++ b/include/xo/printjson/init_printjson.hpp @@ -8,21 +8,21 @@ #include "xo/subsys/Subsystem.hpp" namespace xo { - /* tag to represent the printjson/ subsystem within ordered initialization */ - enum S_printjson_tag {}; + /* tag to represent the printjson/ subsystem within ordered initialization */ + enum S_printjson_tag {}; - /* Use: - * // anywhere, to declare printjson dependency e.g. at file scope - * InitEvidence s_evidence = InitSubsys::require(); - * - * // from main(), though can resort to module initialization in a pybind11 library - * Subsystem::initialize_all(); - */ - template<> - struct InitSubsys { - static void init(); - static InitEvidence require(); - }; + /* Use: + * // anywhere, to declare printjson dependency e.g. at file scope + * InitEvidence s_evidence = InitSubsys::require(); + * + * // from main(), though can resort to module initialization in a pybind11 library + * Subsystem::initialize_all(); + */ + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; } /*namespace xo*/ From 45a0675436c7512398a0750b158d0a1907e6cf27 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:54:39 -0400 Subject: [PATCH 0194/2524] printjson: add printer for nested TaggedPtr's --- src/printjson/PrintJson.cpp | 45 ++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/printjson/PrintJson.cpp b/src/printjson/PrintJson.cpp index 1ac048b6..7df2d5b3 100644 --- a/src/printjson/PrintJson.cpp +++ b/src/printjson/PrintJson.cpp @@ -17,6 +17,7 @@ namespace xo { using xo::reflect::TypeDescr; using xo::reflect::TaggedPtr; using xo::reflect::TaggedRcptr; + using xo::print::quoted; using xo::time::iso8601; using xo::ref::rp; using xo::xtag; @@ -183,6 +184,46 @@ namespace xo { this->print_tp(obj->self_tp(), p_os); } /*print_obj*/ + /* Consider: + * TaggedPtr tp = ...; + * std::ostream * p_os = ...; + * + * PrintJson * pjson = PrintJsonSingleton::instance(); + * + * // print json representation, depending on runtime type of tp's target + * pjson->print_tp(tp, p_os); + * + * // can also use .print(), relying on JsonPrinter_TaggedPtr + * // .print() will next original TaggedPtr in another; + * // this shim unwinds that + * // + * pjson->print(tp, p_os); + */ + class JsonPrinter_TaggedPtr : public JsonPrinter { + public: + JsonPrinter_TaggedPtr(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override { + TaggedPtr * x = this->check_recover_native(tp, p_os); + + if (x) { + this->pjson()->print_tp(*x, p_os); + } + } /*print_json*/ + }; /*JsonPrinter_TaggedPtr*/ + + namespace { + void + provide_tagged_ptr_printer(PrintJson * p_json) + { + std::unique_ptr printer(new JsonPrinter_TaggedPtr(p_json)); + + p_json->provide_printer(Reflect::require(), + std::move(printer)); + } /*provide_tagged_ptr_printer*/ + } /*namespace*/ + class JsonPrinter_bool : public JsonPrinter { public: JsonPrinter_bool(PrintJson const * pjson) : JsonPrinter(pjson) {} @@ -299,7 +340,7 @@ namespace xo { if (x) { /* TODO: escapes special characters */ - *p_os << *x; + *p_os << quoted(*x); } else { report_internal_type_consistency_error(Reflect::require(), tp.td(), @@ -360,6 +401,8 @@ namespace xo { void PrintJson::provide_std_printers() { + provide_tagged_ptr_printer(this); + provide_bool_printer(this); provide_integer_printer(this); From 0a26fa9911f085f22d6aee1dfc49bc9d6ed4314a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 14:55:08 -0400 Subject: [PATCH 0195/2524] printjson: fix+ reinstate unit test --- CMakeLists.txt | 2 +- utest/CMakeLists.txt | 58 +++++++++++++++ utest/CMakeLists.txt.safe | 55 +++++++++++++++ utest/PrintJson.test.cpp | 124 +++++++++++++++++++++++++++++++++ utest/printjson_utest_main.cpp | 6 ++ 5 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 utest/CMakeLists.txt create mode 100644 utest/CMakeLists.txt.safe create mode 100644 utest/PrintJson.test.cpp create mode 100644 utest/printjson_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 028f4c5e..af42e3e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ xo_toplevel_compile_options() # ---------------------------------------------------------------- add_subdirectory(src/printjson) -#add_subdirectory(utest) +add_subdirectory(utest) # ---------------------------------------------------------------- # provide find_package() support for printjson customers diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..0b2177e8 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,58 @@ +# build unittest printjson/utest + +set(SELF_EXE utest.printjson) +set(SELF_SRCS printjson_utest_main.cpp PrintJson.test.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +target_code_coverage(${SELF_EXE} 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_EXE} PUBLIC +# ${PROJECT_SOURCE_DIR} +# ${PROJECT_BINARY_DIR}) + +# ---------------------------------------------------------------- +# dependencies on this codebase + +xo_self_dependency(${SELF_EXE} printjson) + +# ---------------------------------------------------------------- +# dependencies on other codebases + +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# 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_EXE} PUBLIC ${CATCH_INCLUDE_DIR}) + +# supplied from xo_include_options2() +## ---------------------------------------------------------------- +## make standard directories for std:: includes explicit +## so that +## (1) they appear in compile_commands.json. +## (2) clangd (run from emacs lsp-mode) can find them +## +#if(CMAKE_EXPORT_COMPILE_COMMANDS) +# set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES +# ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +#endif() + +# end CMakeLists.txt diff --git a/utest/CMakeLists.txt.safe b/utest/CMakeLists.txt.safe new file mode 100644 index 00000000..60200a81 --- /dev/null +++ b/utest/CMakeLists.txt.safe @@ -0,0 +1,55 @@ +# build unittest printjson/utest + +set(SELF_EXECUTABLE_NAME utest.printjson) +set(SELF_SOURCE_FILES printjson_utest_main.cpp PrintJson.test.cpp) + +add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) + +add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) + +# ---------------------------------------------------------------- +# 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 printjson) + +# ---------------------------------------------------------------- +# 3rd part dependency: catch2: + +find_package(Catch2 2 REQUIRED) + +# need this so that catch2/include appears in compile_commands.json, +# on which lsp integration relies. +# +# See also /nix/store/*-catch2-*/lib/cmake/Catch2/ParseAndAddCatchTests.cmake; +# commands here derived from ^ .cmake file +# +find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") +target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC ${CATCH_INCLUDE_DIR}) + +# ---------------------------------------------------------------- +# make standard directories for std:: includes explicit +# so that +# (1) they appear in compile_commands.json. +# (2) clangd (run from emacs lsp-mode) can find them +# +if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +endif() + +# end CMakeLists.txt diff --git a/utest/PrintJson.test.cpp b/utest/PrintJson.test.cpp new file mode 100644 index 00000000..51259e23 --- /dev/null +++ b/utest/PrintJson.test.cpp @@ -0,0 +1,124 @@ +/* file PrintJson.test.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "xo/printjson/PrintJson.hpp" +#include "xo/printjson/init_printjson.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/reflect/StructReflector.hpp" +#include +#include +#include + +//#define STRINGIFY(x) #x + +namespace xo { + using xo::json::PrintJson; + using xo::reflect::Reflect; + using xo::reflect::StructReflector; + using xo::reflect::TaggedPtr; + + namespace ut { + InitEvidence s_init_evidence = InitSubsys::require(); + + namespace { + struct TestStruct0 {}; + } + + TEST_CASE("print-json-empty-struct", "[printjson]") { + INFO(tag("s_init_evidence", s_init_evidence)); + + StructReflector sr; + + sr.require_complete(); + + TestStruct0 recd0; + + PrintJson print_json; + + TaggedPtr tp = Reflect::make_tp(&recd0); + + std::stringstream ss; + + print_json.print(tp, &ss); + + REQUIRE(ss.str() == std::string("{\"_name_\": \"TestStruct0\"}")); + } /*TEST_CASE(print-json-empty-struct)*/ + + namespace { + struct TestStruct1 { + std::int16_t i16_; std::uint16_t u16_; + std::int32_t i32_; std::uint32_t u32_; + std::int64_t i64_; std::uint64_t u64_; + float f32_; double f64_; + std::string s_; + }; + } + + TEST_CASE("print-json-s1", "[printjson]") { + INFO(tag("s_init_evidence", s_init_evidence)); + + StructReflector sr; + { + REFLECT_MEMBER(sr, i16); + REFLECT_MEMBER(sr, u16); + REFLECT_MEMBER(sr, i32); + REFLECT_MEMBER(sr, u32); + REFLECT_MEMBER(sr, i64); + REFLECT_MEMBER(sr, u64); + REFLECT_MEMBER(sr, f32); + REFLECT_MEMBER(sr, f64); + REFLECT_MEMBER(sr, s); + + sr.require_complete(); + } + + TestStruct1 recd1{-1, 2, -3, 4, -5, 6, 1.23f, 4.56, "hello, world"}; + + PrintJson print_json; + + TaggedPtr tp = Reflect::make_tp(&recd1); + + std::stringstream ss; + + print_json.print(tp, &ss); + + REQUIRE(ss.str() == std::string("{\"_name_\": \"TestStruct1\"" + ", \"i16\": -1" + ", \"u16\": 2" + ", \"i32\": -3" + ", \"u32\": 4" + ", \"i64\": -5" + ", \"u64\": 6" + ", \"f32\": 1.23" + ", \"f64\": 4.56" + ", \"s\": \"hello, world\"}")); + } /*TEST_CASE(print-json-s1)*/ + + TEST_CASE("print-json-v1", "[printjson]") { + INFO(tag("s_init_evidence", s_init_evidence)); + + std::vector v1{1, 2, 3}; + + PrintJson print_json; + + TaggedPtr tp = Reflect::make_tp(&v1); + + std::stringstream ss; + + print_json.print(tp, &ss); + + REQUIRE(ss.str() == std::string("[1, 2, 3]")); + } /*TEST_CASE(print-json-v1)*/ + + /* also see tests: + * [option_util/utest/Px2.test.cpp] + * [option_util/utest/Size2.test.cpp] + * [option_util/utest/PxSize2.test.cpp] + */ + } /*namespace ut */ +} /*namespace xo*/ + + +/* end StructReflector.test.cpp */ diff --git a/utest/printjson_utest_main.cpp b/utest/printjson_utest_main.cpp new file mode 100644 index 00000000..e7c11b66 --- /dev/null +++ b/utest/printjson_utest_main.cpp @@ -0,0 +1,6 @@ +/* file printjson_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end printjson_utest_main.cpp */ From ccb934e2b5fffd2d02d22ed7ce992310b3da3bd5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 16:53:37 -0400 Subject: [PATCH 0196/2524] cosmetic: indentation in init_reflect.hpp --- include/xo/reflect/init_reflect.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/xo/reflect/init_reflect.hpp b/include/xo/reflect/init_reflect.hpp index b6365632..5f69eadd 100644 --- a/include/xo/reflect/init_reflect.hpp +++ b/include/xo/reflect/init_reflect.hpp @@ -8,14 +8,14 @@ #include "xo/subsys/Subsystem.hpp" namespace xo { - /* tag to represent the reflect/ subsystem within ordered initialization */ - enum S_reflect_tag {}; + /* tag to represent the reflect/ subsystem within ordered initialization */ + enum S_reflect_tag {}; - template<> - struct InitSubsys { - static void init(); - static InitEvidence require(); - }; + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; } /*namespace xo*/ From 6da0ceb35d88a326b567ab40c5206eeb5289602c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 9 Oct 2023 16:57:05 -0400 Subject: [PATCH 0197/2524] subsys: + operator<< for InitEvidence --- include/xo/subsys/Subsystem.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/xo/subsys/Subsystem.hpp b/include/xo/subsys/Subsystem.hpp index ece72db6..3cc6012a 100644 --- a/include/xo/subsys/Subsystem.hpp +++ b/include/xo/subsys/Subsystem.hpp @@ -6,6 +6,7 @@ #pragma once #include "xo/indentlog/scope.hpp" +#include #include #include #include @@ -57,6 +58,12 @@ namespace xo { std::uint64_t evidence_ = 0; }; /*InitEvidence*/ + inline std::ostream & + operator<<(std::ostream & os, InitEvidence x) { + os << ""; + return os; + } /*operator<<*/ + /* Goals: * 1. provide for code that must run once (and only once) * to initialize subsystems From 487f516a3fd513e92fa75a338e13994626d7090f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 12:32:34 -0400 Subject: [PATCH 0198/2524] initial implementation --- CMakeLists.txt | 48 +++++ cmake/callbackConfig.cmake.in | 6 + include/xo/callback/CallbackSet.hpp | 312 ++++++++++++++++++++++++++++ src/callback/CMakeLists.txt | 14 ++ src/callback/CallbackSet.cpp | 22 ++ 5 files changed, 402 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/callbackConfig.cmake.in create mode 100644 include/xo/callback/CallbackSet.hpp create mode 100644 src/callback/CMakeLists.txt create mode 100644 src/callback/CallbackSet.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..ada7743d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,48 @@ +# callback/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(callback VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# 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 + +# PROJECT_CXX_FLAGS: bespoke for this project - usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/callback) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support to customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/cmake/callbackConfig.cmake.in b/cmake/callbackConfig.cmake.in new file mode 100644 index 00000000..f7176f38 --- /dev/null +++ b/cmake/callbackConfig.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(refcnt) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp new file mode 100644 index 00000000..2abb2c04 --- /dev/null +++ b/include/xo/callback/CallbackSet.hpp @@ -0,0 +1,312 @@ +/* @file CallbackSet.hpp */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +//#include "indentlog/scope.hpp" +//#include "indentlog/print/tag.hpp" +#include +#include + +namespace xo { + namespace fn { + /* identifies a particular callback in a CallbackSet (see below). + * an unique id is created: + * CallbackSetImpl cbset = ...; + * CallbackId cb_id = cbset.add_callback(..); + * + * can use id to remove callback later: + * cbset.remove_callback(cb_id); + */ + class CallbackId { + public: + CallbackId() = default; + explicit CallbackId(uint32_t id) : id_{id} {} + + /* generate a globally-unique id (not threadsafe) */ + static CallbackId generate(); + + uint32_t id() const { return id_; } + + private: + uint32_t id_ = 0; + }; /*CallbackId*/ + + inline bool operator==(CallbackId lhs, CallbackId rhs) { return lhs.id() == rhs.id(); } + inline bool operator!=(CallbackId lhs, CallbackId rhs) { return lhs.id() != rhs.id(); } + + /* queue add/remove callback instructions encountered during callback + * execution, to avoid invalidating vector iterator. + * + */ + template + struct ReentrantCbsetCmd { + enum CbsetCmdEnum { AddCallback, RemoveCallback }; + + ReentrantCbsetCmd() = default; + ReentrantCbsetCmd(CbsetCmdEnum cmd, CallbackId id, Fn const & fn) + : cmd_{cmd}, id_{id}, fn_{fn} {} + + static ReentrantCbsetCmd add(CallbackId id, Fn const & fn) { + return ReentrantCbsetCmd{AddCallback, id, fn}; + } /*add*/ + + static ReentrantCbsetCmd remove(CallbackId id) { + return ReentrantCbsetCmd{RemoveCallback, id, Fn()}; + } /*remove*/ + + bool is_add() const { return cmd_ == AddCallback; } + bool is_remove() const { return cmd_ == RemoveCallback; } + CallbackId id() const { return id_; } + Fn const & fn() const { return fn_; } + + private: + /* AddCallback: deferred CallbackSet::add_callback(.fn) + * RemoveCallback: deferred CallbackSet::remove_callback(.fn) + */ + CbsetCmdEnum cmd_ = AddCallback; + CallbackId id_; + Fn fn_; + }; /*ReentrantCbsetCmd*/ + + /* record for remembering a single callback. + * callbacks are given unique ids so they can be removed later + */ + template + struct CbRecd { + CbRecd(CallbackId id, Fn const & fn) : id_{id}, fn_{fn} {} + + CallbackId id_; + Fn fn_; + }; /*CbRecd*/ + + /* If Fnptr is a type such that this works: + * Fnptr fn = ...; + * using Fn = Fnptr::destination_type; + * Fn * native_fn = fn.get(); + * (native_fn->*member_fn)(args ...); + * + * then + * CallbackSet cbset = ...; + * cbset.invoke(&Fn::member_fn, args...) + * + * calls + * (cb->*member_fn)(args...) + * + * for each callback cb in this set. + * + * In addition, calls hook methods: + * cb->notify_add_callback() + * cb->notify_remove_callback() + * when adding/removing callback. + * + * Require: + * - can invoke (Fnptr->*member_fn)(...) + * + * implementation is reentrant: running callbacks can safely make + * add/remove calls on the cbset that invoked them. + * + * not threadsafe. + */ + template + class CallbackSetImpl { + public: + using callback_type = typename Fn::element_type; + //using scope = xo::scope; + + public: + CallbackSetImpl() = default; + + /* support for range iterators */ + typename std::vector>::const_iterator begin() const { return cb_v_.begin(); } + typename std::vector>::const_iterator end() const { return cb_v_.end(); } + + /* invoke callbacks registered with this callback set */ + template + void invoke(void (callback_type::* member_fn)(Sn... args), Tn&&... args) { + this->cb_running_ = true; + + try { + for(CbRecd const & cb_recd : this->cb_v_) { + callback_type * native_cb = cb_recd.fn_.get(); + + /* clang11 doesn't like + * cb->*member_fn + * when cb-> is overloaded + */ + (native_cb->*member_fn)(args...); + } + + this->make_deferred_changes(); + } catch(...) { + this->make_deferred_changes(); + throw; + } + } /*operator()*/ + + /* call fn(cb) for each callback present in this set */ + void visit_callbacks(std::function fn) const { + CallbackSetImpl * self = const_cast(this); + + self->cb_running_ = true; + + try { + for(Fn const & cb : this->cb_v_) + fn(cb); + + this->make_deferred_changes(); + } catch(...) { + this->make_deferred_changes(); + throw; + } + } /*visit_callbacks*/ + + /* add callback target_fn to this callback set. + * reentrant + */ + CallbackId add_callback(Fn const & target_fn) { + CallbackId id = CallbackId::generate(); + + if(this->cb_running_) { + /* defer until callback execution completes */ + this->reentrant_cmd_v_.push_back(ReentrantCbsetCmd::add(id, target_fn)); + } else { +#ifdef NOT_USING + constexpr bool c_debug_enabled_flag = false; + scope lscope(reflect::type_name(), + "::add_callback", c_debug_enabled_flag); + + if (c_debug_enabled_flag) { + lscope.log("before appending .cb_v[]", + xo::xtag("target_fn", (void*)target_fn.get()), + xo::xtag("target_fn.refcnt", + target_fn->reference_counter())); + } +#endif + + this->cb_v_.push_back(CbRecd(id, target_fn)); + +#ifdef NOT_USING + if (c_debug_enabled_flag) { + lscope.log("after appending .cb_v[]", + xo::xtag("target_fn", (void *)target_fn.get()), + xo::xtag("target_fn.refcnt", + target_fn->reference_counter())); + } +#endif + } + + return id; + } /*add_callback*/ + + void remove_callback(CallbackId id) { + if(this->cb_running_) { + /* defer until callback execution completes */ + this->reentrant_cmd_v_.push_back(ReentrantCbsetCmd::remove(id)); + } else { + this->remove_callback_impl(id); + } + + } /*remove_callback*/ + +#ifdef NOT_USING + /* remove callback target_fn from this callback set. + * noop if callback is not present + */ + void remove_callback(Fn const & target_fn) { + if(this->cb_running_) { + /* defer until callback execution completes */ + this->reentrant_cmd_v_.push_back(ReentrantCbsetCmd::remove(target_fn)); + } else { + this->remove_callback_impl(target_fn); + } + } /*remove_callback*/ +#endif + + private: + /* apply deferred changes to .cb_v[] */ + void make_deferred_changes() { + this->cb_running_ = false; + + std::vector> cmd_v; + std::swap(cmd_v, this->reentrant_cmd_v_); + + for(ReentrantCbsetCmd const & cmd : cmd_v) { + if(cmd.is_add()) { + this->cb_v_.push_back(CbRecd(cmd.id(), cmd.fn())); + + cmd.fn()->notify_add_callback(); + } else if(cmd.is_remove()) { + this->remove_callback_impl(cmd.id()); + } + } + } /*make_deferred_changes*/ + + void remove_callback_impl(CallbackId target_id) { + for (auto ix = this->cb_v_.begin(); ix != this->cb_v_.end(); ++ix) { + if (ix->id_ == target_id) { + Fn target_fn = ix->fn_; + + this->cb_v_.erase(ix); + + target_fn->notify_remove_callback(); + break; + } + } + } /*remove_callback_impl*/ + +#ifdef NOT_USING + void remove_callback_impl(Fn const & target_fn) { + auto ix = std::find(this->cb_v_.begin(), this->cb_v_.end(), target_fn); + + if(ix != this->cb_v_.end()) + this->cb_v_.erase(ix); + + target_fn->notify_remove_callback(); + } /*remove_callback_impl*/ +#endif + + private: + bool cb_running_ = false; + /* collection of callback functions */ + std::vector> cb_v_; + /* when a callback registered with *this, while running, + * attempts to add/remove a callback to/from this set + * (including removing itself), + * must defer until all callbacks have executed. + * remember deferred instructions here. + */ + std::vector> reentrant_cmd_v_; + }; /*CallbackSetImpl*/ + + template + using RpCallbackSet = CallbackSetImpl>; + + /* like RpCallbackSet, + * but also provides overload(s) for operator()(..) + */ + template + class NotifyCallbackSet : public RpCallbackSet { + public: + NotifyCallbackSet(MemberFn fn) + : privileged_member_fn_{fn} {} + + template + void operator()(Tn&&... args) { + this->invoke(this->privileged_member_fn_, args...); + } /*operator()*/ + + private: + /* implements operator()(...) */ + MemberFn privileged_member_fn_; + }; /*NotifyCallbackSet*/ + + template + inline NotifyCallbackSet + make_notify_cbset(Sret (NativeFn::* member_fn)(Sn...)) { + return NotifyCallbackSet(member_fn); + } /*make_notify_cbset*/ + } /*namespace fn*/ +} /*namespace xo*/ + +/* end CallbackSet.hpp */ diff --git a/src/callback/CMakeLists.txt b/src/callback/CMakeLists.txt new file mode 100644 index 00000000..58dc3de5 --- /dev/null +++ b/src/callback/CMakeLists.txt @@ -0,0 +1,14 @@ +# callback/CMakeLists.txt + +set(SELF_LIB callback) +set(SELF_SRCS CallbackSet.cpp) + +# reminder: can't be header-only library, because depends on non-header-only refcnt +xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies: + +xo_dependency(${SELF_LIB} refcnt) + +# end CMakeLists.txt diff --git a/src/callback/CallbackSet.cpp b/src/callback/CallbackSet.cpp new file mode 100644 index 00000000..881f57f8 --- /dev/null +++ b/src/callback/CallbackSet.cpp @@ -0,0 +1,22 @@ +/* file CallbackSet.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "CallbackSet.hpp" + +namespace xo { + namespace fn { + CallbackId + CallbackId::generate() + { + static CallbackId s_last_id; + + s_last_id = CallbackId(s_last_id.id() + 1); + + return s_last_id; + } /*generate*/ + } /*namespace fn*/ +} /*namespace xo*/ + +/* end CallbackSet.cpp */ From e4f54cb2f75a973df28f9a22864e76a2c1fbfe8a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 12:33:30 -0400 Subject: [PATCH 0199/2524] + .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9e716afc --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# lsp keeps state here +.cache +# typical build directories +build +ccov From 9644d6b966f6640823cd34f28bf9ca7385ecaedb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 12:44:14 -0400 Subject: [PATCH 0200/2524] + README.md --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..fcfa8e8a --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# callback-set with reentrant invocation + +Reentrant: +1. A callback can modify parent callback-set (for example to remove itself), + even while being invoked. +2. Any such re-entrant operations are deferred until callback invocation completes. + +# build + install +``` +$ cd xo-callback +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +# build for unit test coverage +``` +$ cd xo-callback +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +# LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +cmake creates them in the root of its build directory. +``` +$ cd xo-callback +$ ln -s build/compile_commands.json +``` From 437f943e37160972cae569d1d0b5e5b6b830728b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 12:59:54 -0400 Subject: [PATCH 0201/2524] cosmetic: comment adjustments --- include/xo/callback/CallbackSet.hpp | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp index 2abb2c04..05f6c5fa 100644 --- a/include/xo/callback/CallbackSet.hpp +++ b/include/xo/callback/CallbackSet.hpp @@ -82,7 +82,7 @@ namespace xo { /* If Fnptr is a type such that this works: * Fnptr fn = ...; - * using Fn = Fnptr::destination_type; + * using Fn = Fnptr::element_type; * Fn * native_fn = fn.get(); * (native_fn->*member_fn)(args ...); * @@ -101,6 +101,8 @@ namespace xo { * when adding/removing callback. * * Require: + * - Fnptr::element_type + * - Fnptr::get() -> Fnptr::element_type const * * - can invoke (Fnptr->*member_fn)(...) * * implementation is reentrant: running callbacks can safely make @@ -130,7 +132,7 @@ namespace xo { for(CbRecd const & cb_recd : this->cb_v_) { callback_type * native_cb = cb_recd.fn_.get(); - /* clang11 doesn't like + /* clang11 doesn't like (with cb=cb_recd.fn_) * cb->*member_fn * when cb-> is overloaded */ @@ -255,17 +257,6 @@ namespace xo { } } /*remove_callback_impl*/ -#ifdef NOT_USING - void remove_callback_impl(Fn const & target_fn) { - auto ix = std::find(this->cb_v_.begin(), this->cb_v_.end(), target_fn); - - if(ix != this->cb_v_.end()) - this->cb_v_.erase(ix); - - target_fn->notify_remove_callback(); - } /*remove_callback_impl*/ -#endif - private: bool cb_running_ = false; /* collection of callback functions */ @@ -297,7 +288,7 @@ namespace xo { } /*operator()*/ private: - /* implements operator()(...) */ + /* implements NotifyCallbackSet's operator()(...) */ MemberFn privileged_member_fn_; }; /*NotifyCallbackSet*/ From d4c486abf13bd5d1fcc3b22c8bab9113241ce3a5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 13:00:15 -0400 Subject: [PATCH 0202/2524] cosmetic: comment --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ada7743d..1735ddc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ include(xo_macros/xo_cxx) include(xo_macros/code-coverage) # ---------------------------------------------------------------- -# unit test setup +# unit test setup (for consistency with other xo libraries. no unit tests as of 10oct2023) enable_testing() # activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON) From 75ee64909e9aa20e66cf4924c77faf368536151c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 13:00:28 -0400 Subject: [PATCH 0203/2524] github: workflow --- .github/workflows/main.yml | 100 +++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..0971d8de --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,100 @@ +# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml +name: CMake on a single platform + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Configure self (callback) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (callback) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Test self (callback) + working-directory: ${{github.workspace}}/build_callback + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 4d2cc526585116aed8a9a90cf948cad0085d6490 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 13:03:45 -0400 Subject: [PATCH 0204/2524] refcnt: + indentlog dep in generated cmake config --- cmake/refcntConfig.cmake.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/refcntConfig.cmake.in b/cmake/refcntConfig.cmake.in index 9c15f36a..5b38fa74 100644 --- a/cmake/refcntConfig.cmake.in +++ b/cmake/refcntConfig.cmake.in @@ -1,4 +1,6 @@ @PACKAGE_INIT@ +include(CMakeFindDependencyMacro) +find_dependency(indentlog) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From 5b8f2b6d7458e5e8e7614ce0167cff331f0867d8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 13:07:04 -0400 Subject: [PATCH 0205/2524] cosmetic: comments --- include/xo/callback/CallbackSet.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp index 05f6c5fa..c15aeb34 100644 --- a/include/xo/callback/CallbackSet.hpp +++ b/include/xo/callback/CallbackSet.hpp @@ -65,7 +65,9 @@ namespace xo { * RemoveCallback: deferred CallbackSet::remove_callback(.fn) */ CbsetCmdEnum cmd_ = AddCallback; + /* operate on callback with this id */ CallbackId id_; + /* callback function to add/remove */ Fn fn_; }; /*ReentrantCbsetCmd*/ From 532d48529f708234d3aac178ab4dc3d51b684147 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 15:20:32 -0400 Subject: [PATCH 0206/2524] initial implementation --- CMakeLists.txt | 51 +++ README.md | 42 ++ cmake/reactorConfig.cmake.in | 13 + include/xo/reactor/AbstractEventProcessor.hpp | 50 +++ include/xo/reactor/AbstractSink.hpp | 71 ++++ include/xo/reactor/AbstractSource.hpp | 94 +++++ include/xo/reactor/DirectSource.hpp | 19 + include/xo/reactor/DirectSourcePtr.hpp | 25 ++ include/xo/reactor/EventSource.hpp | 25 ++ include/xo/reactor/EventStore.hpp | 317 ++++++++++++++++ include/xo/reactor/EventTimeFn.hpp | 38 ++ include/xo/reactor/HeapReducer.hpp | 72 ++++ include/xo/reactor/LastReducer.hpp | 154 ++++++++ include/xo/reactor/PollingReactor.hpp | 44 +++ include/xo/reactor/PolyAdapterSink.hpp | 92 +++++ include/xo/reactor/Reactor.hpp | 63 +++ include/xo/reactor/ReactorSource.hpp | 130 +++++++ include/xo/reactor/Reducer.hpp | 33 ++ include/xo/reactor/SecondarySource.hpp | 359 ++++++++++++++++++ include/xo/reactor/Sink.hpp | 222 +++++++++++ include/xo/reactor/init_reactor.hpp | 20 + src/reactor/AbstractEventProcessor.cpp | 93 +++++ src/reactor/AbstractSource.cpp | 84 ++++ src/reactor/CMakeLists.txt | 21 + src/reactor/PollingReactor.cpp | 88 +++++ src/reactor/Reactor.cpp | 26 ++ src/reactor/ReactorSource.cpp | 34 ++ src/reactor/Sink.cpp | 18 + src/reactor/init_reactor.cpp | 31 ++ 29 files changed, 2329 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/reactorConfig.cmake.in create mode 100644 include/xo/reactor/AbstractEventProcessor.hpp create mode 100644 include/xo/reactor/AbstractSink.hpp create mode 100644 include/xo/reactor/AbstractSource.hpp create mode 100644 include/xo/reactor/DirectSource.hpp create mode 100644 include/xo/reactor/DirectSourcePtr.hpp create mode 100644 include/xo/reactor/EventSource.hpp create mode 100644 include/xo/reactor/EventStore.hpp create mode 100644 include/xo/reactor/EventTimeFn.hpp create mode 100644 include/xo/reactor/HeapReducer.hpp create mode 100644 include/xo/reactor/LastReducer.hpp create mode 100644 include/xo/reactor/PollingReactor.hpp create mode 100644 include/xo/reactor/PolyAdapterSink.hpp create mode 100644 include/xo/reactor/Reactor.hpp create mode 100644 include/xo/reactor/ReactorSource.hpp create mode 100644 include/xo/reactor/Reducer.hpp create mode 100644 include/xo/reactor/SecondarySource.hpp create mode 100644 include/xo/reactor/Sink.hpp create mode 100644 include/xo/reactor/init_reactor.hpp create mode 100644 src/reactor/AbstractEventProcessor.cpp create mode 100644 src/reactor/AbstractSource.cpp create mode 100644 src/reactor/CMakeLists.txt create mode 100644 src/reactor/PollingReactor.cpp create mode 100644 src/reactor/Reactor.cpp create mode 100644 src/reactor/ReactorSource.cpp create mode 100644 src/reactor/Sink.cpp create mode 100644 src/reactor/init_reactor.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..9bbe8050 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,51 @@ +# xo-reactor/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(reactor VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# 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 + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(src/reactor) + +# ---------------------------------------------------------------- +# provide find_pacakge() support for reactor customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install .hpp files + +xo_install_include_tree() + +# end CMakeLists.txt diff --git a/README.md b/README.md new file mode 100644 index 00000000..02b7e7a2 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# reactor library + +in-memory queuing system + +# dependencies + +build+install these first + +- xo-reflect [github.com/Rconybea/xo-reflect] +- xo-callback [github.com/Rconybea/xo-callback] + +# build + install + +# build +``` +$ cd reactor +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +# build for unit test coverage +``` +$ cd xo-reactor +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +# LSP support + +LSP looks for compile commands in the root of the source tree; +cmake creates them in the root of its build directory. + +``` +$ cd xo-reactor +$ ln -s build/compile_commands.json +``` diff --git a/cmake/reactorConfig.cmake.in b/cmake/reactorConfig.cmake.in new file mode 100644 index 00000000..5456d16b --- /dev/null +++ b/cmake/reactorConfig.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +find_dependency(reflect) +find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/reactor/AbstractEventProcessor.hpp b/include/xo/reactor/AbstractEventProcessor.hpp new file mode 100644 index 00000000..820ab0ae --- /dev/null +++ b/include/xo/reactor/AbstractEventProcessor.hpp @@ -0,0 +1,50 @@ +/* @file AbstractEventProcessor.hpp */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include +#include +#include + +namespace xo { + namespace reactor { + /* common base class for {AbstractSource, AbstractSink}. + * An event processor can be: + * 1. an event source (inherits AbstractSource) + * 2. an event sink (inherits AbstractSink) + * 3. both source+sink (inherits both) + */ + class AbstractEventProcessor : virtual public ref::Refcount { + public: + /* reporting name for this source. ideally unique, but not required */ + virtual std::string const & name() const = 0; + /* set .name */ + virtual void set_name(std::string const & x) = 0; + + /* find all event processors ep reachable from x (i.e. downstream from x). + * report each such ep exactly once + */ + static std::vector> map_network(ref::rp const & x); + + /* visit direct downstream consumers c[i] of this event processor. + * call ep(c[i]) for each such consumer. + */ + virtual void visit_direct_consumers(std::function ep)> const & fn) = 0; + + /* write representation to stream */ + virtual void display(std::ostream & os) const = 0; + /* human-readable string identifying this source */ + virtual std::string display_string() const; + }; /*AbstractEventProcessor*/ + + inline std::ostream & + operator<<(std::ostream & os, AbstractEventProcessor const & src) { + src.display(os); + return os; + } /*operator<<*/ + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end AbstractEventProcessor.hpp */ diff --git a/include/xo/reactor/AbstractSink.hpp b/include/xo/reactor/AbstractSink.hpp new file mode 100644 index 00000000..e855eda8 --- /dev/null +++ b/include/xo/reactor/AbstractSink.hpp @@ -0,0 +1,71 @@ +/* @file AbstractSink.hpp */ + +#pragma once + +#include "AbstractSource.hpp" +#include "xo/reflect/TaggedPtr.hpp" +#include "xo/reflect/TypeDescr.hpp" +//#include "time/Time.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/cxxutil/demangle.hpp" +#include + +namespace xo { + namespace reactor { + /* an event consumer. + * note that event representation is not specified, + * this helps avoid mandating a type hierarchy for events + */ + class AbstractSink : public virtual AbstractEventProcessor { + public: + using TypeDescr = reflect::TypeDescr; + using TaggedPtr = reflect::TaggedPtr; + + public: + virtual ~AbstractSink() = default; + + /* if true: sources may produce events of any reflected type. + * sink will accept such events using .notify_ev_tp() + * for example see web_util/WebsocketSink + * + * if false (common): souce is expected to to produce events of + * a single type, specified by .sink_ev_type() + * .notify_ev_tp() will downcast to that type. + * for example see reactor/Sink1 + * + * polymorphic sinks pay for runtime polymorphism + * (since WebsocketSink sends events in json format this is + * expected to be negligible compared to message formatting) + */ + virtual bool allow_polymorphic_source() const = 0; + + /* identify datatype for items expected by this sink */ + virtual TypeDescr sink_ev_type() const = 0; + + /* true iff this sink accepts volatile events. + * volatile events are events that may be modified + * or destroyed after being delivered to this sink. + * + * For example KalmanFilterSvc accepts volatile events, + * but EventStore requires non-volatile events. + */ + virtual bool allow_volatile_source() const = 0; + + /* counts lifetime #of incoming events for this sink */ + virtual uint32_t n_in_ev() const = 0; + + /* attach an input source. + * typically this means calling src.add_callback() + * with a function thats calls a .notify_xxx() method + * on this Sink + */ + virtual void attach_source(ref::rp const & src) = 0; + + /* accept incoming event, given by tagged pointer */ + virtual void notify_ev_tp(TaggedPtr const & ev_tp) = 0; + }; /*AbstractSink*/ + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end AbstractSink.hpp */ diff --git a/include/xo/reactor/AbstractSource.hpp b/include/xo/reactor/AbstractSource.hpp new file mode 100644 index 00000000..ed31d04f --- /dev/null +++ b/include/xo/reactor/AbstractSource.hpp @@ -0,0 +1,94 @@ +/* @file AbstractSource.hpp */ + +#pragma once + +#include "AbstractEventProcessor.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include "xo/callback/CallbackSet.hpp" +#include "xo/refcnt/Refcounted.hpp" +#include + +namespace xo { + namespace web { class StreamEndpointDescr; } + + namespace reactor { + class AbstractSink; + + template + class Sink1; + + /* abstract api for a source of events. + * Event representation is left open: Sources and Sinks + * need to have compatible event representations, + * and coordination is left to such (Source, Sink) pairs. + * + * See ReactorSource, for example + * + * Typically a Source will have one or more .add_callback() + * methods, for listening to source events + */ + class AbstractSource : public virtual AbstractEventProcessor { + public: + using StreamEndpointDescr = web::StreamEndpointDescr; + using TypeDescr = reflect::TypeDescr; + using CallbackId = fn::CallbackId; + + public: + /* identify datatype for items delivered by this source */ + virtual TypeDescr source_ev_type() const = 0; + + /* if true: event objects (see .source_ev_type()) + * maybe overwritten between callbacks. + * A sink that wants to capture events + * (e.g. EventStore<>) will need to deep-copy them + * if false: event objects are preserved between callbacks. + */ + virtual bool is_volatile() const = 0; + + /* counts #of outbound events ready for delivery, + * but not yet sent */ + virtual uint32_t n_queued_out_ev() const = 0; + /* counts lifetime #of events delivered. + * see also AbstractSink.n_in_ev + */ + virtual uint32_t n_out_ev() const = 0; + + /* if true, simulator will report interaction with this source */ + virtual bool debug_sim_flag() const = 0; + /* set .trace_sim_flag */ + virtual void set_debug_sim_flag(bool x) = 0; + + virtual CallbackId attach_sink(ref::rp const & sink) = 0; + virtual void detach_sink(CallbackId id) = 0; + + /* endpoint for a websocket subscriber; + * subscriber delivers events produced by this source + */ + StreamEndpointDescr stream_endpoint_descr(std::string const & url_prefix); + + /* typically expect events to be delivered using a reactor or simulator. + * (for example see reactor/Reactor, simulator/Simulator); + * reactor allocates cpu, and controls event ordering across sources + * when there are multiple sources. + * + * However, also possible for user code to invoke .deliver_one() directly. + * Beware, may get unpredictable results if attempt to do this on a source + * that's also attached to a reactor. + */ + virtual std::uint64_t deliver_one() = 0; + + /* convenience: call .deliver_one() n times, return sum of results */ + std::uint64_t deliver_n(uint64_t n); + + /* convenience: call .deliver_one() until it returns 0 + * (beware of inexhaustible sources!) + */ + std::uint64_t deliver_all(); + }; /*AbstractSource*/ + + using AbstractSourcePtr = ref::rp; + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end AbstractSource.hpp */ diff --git a/include/xo/reactor/DirectSource.hpp b/include/xo/reactor/DirectSource.hpp new file mode 100644 index 00000000..03710281 --- /dev/null +++ b/include/xo/reactor/DirectSource.hpp @@ -0,0 +1,19 @@ +/* @file DirectSource.hpp */ + +#pragma once + +#include "time/Time.hpp" +#include "reactor/Sink.hpp" +#include "reactor/EventSource.hpp" +#include "reactor/HeapReducer.hpp" +//#include "reactor/LastReducer.hpp" +#include "reactor/Reactor.hpp" +#include "callback/CallbackSet.hpp" + +namespace xo { + namespace reactor { + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end DirectSource.hpp */ + diff --git a/include/xo/reactor/DirectSourcePtr.hpp b/include/xo/reactor/DirectSourcePtr.hpp new file mode 100644 index 00000000..db9c7734 --- /dev/null +++ b/include/xo/reactor/DirectSourcePtr.hpp @@ -0,0 +1,25 @@ +/* @file DirectSourcePtr.hpp */ + +#pragma once + +#include "reactor/SecondarySource.hpp" +#include "reactor/LastReducer.hpp" +#include "reactor/EventTimeFn.hpp" + +namespace xo { + namespace reactor { + template + using DirectSource = SecondarySource>>; + + /* use when Event is ref::rp for some T */ + template + using DirectSourcePtr = SecondarySource>>; + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end DirectSourcePtr.hpp */ diff --git a/include/xo/reactor/EventSource.hpp b/include/xo/reactor/EventSource.hpp new file mode 100644 index 00000000..a60297f7 --- /dev/null +++ b/include/xo/reactor/EventSource.hpp @@ -0,0 +1,25 @@ +/* @file EventSource.hpp */ + +#pragma once + +#include "reactor/ReactorSource.hpp" +#include "callback/CallbackSet.hpp" + +namespace xo { + namespace reactor { + template + class EventSource : public ReactorSource { + public: + using CallbackId = fn::CallbackId; + + public: + virtual CallbackId add_callback(ref::rp const & cb) = 0; + virtual void remove_callback(CallbackId id) = 0; + }; /*EventSource*/ + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end EventSource.hpp */ diff --git a/include/xo/reactor/EventStore.hpp b/include/xo/reactor/EventStore.hpp new file mode 100644 index 00000000..62ab7f9a --- /dev/null +++ b/include/xo/reactor/EventStore.hpp @@ -0,0 +1,317 @@ +/* @file EventStore.hpp */ + +#pragma once + +#include "reactor/Reducer.hpp" +#include "reactor/EventTimeFn.hpp" +#include "reactor/Sink.hpp" +#include "web_util/HttpEndpointDescr.hpp" +#include "printjson/PrintJson.hpp" +#include "reflect/Reflect.hpp" +#include "tree/RedBlackTree.hpp" + +namespace xo { + namespace reactor { + + /* abstract event store api */ + class AbstractEventStore : virtual public ref::Refcount { + public: + using PrintJson = xo::json::PrintJson; + using TaggedPtr = xo::reflect::TaggedPtr; + using HttpEndpointDescr = xo::web::HttpEndpointDescr; + using Alist = xo::web::Alist; + + public: + /* true iff .size() == 0 */ + virtual bool empty() const = 0; + + /* #of events currently held in this store */ + virtual std::uint32_t size() const = 0; + + /* TODO: + * 1. TaggedGptr = discriminated union of + * a. TaggedRcptr (i.e. refcounted semantics) + * b. Unique (i.e. unique_ptr semantics) + * c. Exptr (i.e. unowned_ptr semantics) + * d. compact (special-case -- value fits in pointer) + * will need mpl copy/assign stuff for TaggedUnique + * 2. provide .last_n(), .last_dt() + */ + + virtual void http_snapshot(ref::rp const & pjson, + std::ostream * p_os) const = 0; + + /* http endpoint; generates http output for this eventstore */ + virtual HttpEndpointDescr http_endpoint_descr(ref::rp const & pjson, + std::string const & url_prefix) const { + + /* important that lambda contains its own rp; + * reference to stack will not do + */ + ref::rp pjson_rp = pjson; + + auto http_fn = ([this, pjson_rp] + (std::string const & /*uri*/, + Alist const & /*alist*/, + std::ostream * p_os) + { + /* WARNING: race condition here, + * given webserver runs from a separate thread + */ + + this->http_snapshot(pjson_rp, p_os); + }); + + return HttpEndpointDescr(url_prefix + "/snap", http_fn); + } /*http_endpoint_descr*/ + + virtual void clear() = 0; + + virtual void insert_tp(TaggedPtr const & ev_tp) = 0; + }; /*AbstractEventStore*/ + + /* in-memory storage for a set of events. + * + * Require: + * - Event is null-constructible + * - Event is copyable + * - EventTimeFn :: Event -> utc_nanos + * + * inheritance + * ref::Refcount + * ^ + * isa + * | + * reactor::AbstractEventProcessor + req .visit_direct_consumers() + * ^ + * isa + * | + * reactor::AbstractSink + req .sink_ev_type(), .notify_ev() etc. + * ^ + * isa + * | + * reactor::Sink1 + .attach_source(), .sink_ev_type(), + * ^ req .notify_ev() etc + * | + * isa + * | + * reactor::SinkEndpoint + impl .visit_direct_consumers() + * ^ + * isa + * | + * reactor::StructEventStore + .last_n() .last_dt() etc. + */ + template + class EventStoreImpl : public SinkEndpoint, + public AbstractEventStore, + ReducerBase + { + static_assert(EventTimeConcept); + + public: + using utc_nanos = xo::time::utc_nanos; + using nanos = xo::time::nanos; + using EventTree = xo::tree::RedBlackTree>; + using PrintJson = xo::json::PrintJson; + using Alist = xo::web::Alist; + using HttpEndpointDescr = xo::web::HttpEndpointDescr; + + static ref::rp make() { return new EventStoreImpl(); } + + /* visit most recent n events in this store. + * returns #of events actually visited + * + * if events visited are e1 .. en, then: + * (1) en is the most recent recorded event + * (.event_tm(en) is .tree.max_key()) + * (2) there are no events between e(i) and e(i+1) + * (i.e. visit does not skip over any events) + * (3) if v < n, then v = .size(), + * where v is the #of events visited + * + * require: + * - Fn :: (Event -> ) + */ + template + std::uint32_t visit_last_n(std::uint32_t n, Fn && fn) const { + std::uint32_t z = this->size(); + std::uint32_t lo = ((n >= z) ? 0 : z - n); + + typename EventTree::const_iterator lo_ix = this->tree_.find_ith(lo); + typename EventTree::const_iterator hi_ix = this->tree_.cend(); + + return this->visit_range(lo_ix, hi_ix, fn); + } /*visit_last_n*/ + + /* visit suffix of events sufficient to cover interval of length dt. + * visit events in increasing timestamp order. + * + * if events visited are e1 .. en, then: + * (1) en is the most recent recorded event + * (.event_tm(en) is .tree.max_key()) + * (2) there are no events between e(i) and e(i+1) + * (i.e. visit does not skip over any events) + * (3) if .event_tm(en) - .event_tm(e1) < dt, + * then e1 is the earliest recorded event + * (.event_tm(e1) is .tree.min_key()) + * (4) if .event_tm(en) - .event_tm(e1) > dt, + * then (.event_tm(en) - .event_tm(e2)) < dt + * + * |<---------- dt ----------->| + * ^ ^ ^ + * e1 e2 en + */ + template + std::uint32_t visit_last_dt(nanos dt, Fn && fn) const { + if (tree_.empty()) + return 0; + + /* tree not empty -> has max key */ + utc_nanos tn = this->tree_.max_key(); + utc_nanos tk = tn - dt; + + typename EventTree::const_iterator lo_ix = this->tree_.find_glb(tk, true /*closed*/); + typename EventTree::const_iterator hi_ix = this->tree_.end(); + + return this->visit_range(lo_ix, hi_ix, fn); + } /*visit_last_dt*/ + + std::vector last_n(std::uint32_t n) const { + std::vector retval; + + auto fn = [&retval](Event const &ev) { retval.push_back(ev); }; + + this->visit_last_n(n, fn); + + return retval; + } /*last_n*/ + + std::vector last_dt(nanos dt) const { + std::vector retval; + + auto fn = [&retval](Event const &ev) { retval.push_back(ev); }; + + this->visit_last_dt(dt, fn); + + return retval; + } /*last_dt*/ + + void insert(Event const & ev) { this->tree_.insert(typename EventTree::value_type(this->event_tm(ev), ev)); } + + // ----- Inherited from AbstractEventStore ----- + + virtual bool empty() const override { return tree_.empty(); } + virtual std::uint32_t size() const override { return tree_.size(); } + + /* write http snapshot of current state to *p_os */ + virtual void http_snapshot(ref::rp const & pjson, std::ostream * p_os) const override { + using xo::reflect::Reflect; + + /* visit last 100 events; + * write them to *p_os in increasing time order + */ + auto ev_v = this->last_n(100); + + pjson->print_tp(Reflect::make_tp(&ev_v), p_os); + } /*http_snapshot*/ + + virtual void clear() override { this->tree_.clear(); } + + virtual void insert_tp(TaggedPtr const & ev_tp) override { + using xo::xtag; + + Event * p_ev = ev_tp.recover_native(); + + if (p_ev) { + this->insert(*p_ev); + } else { + throw std::runtime_error(tostr("StructEventStore::insert_tp" + ": unable to convert ev_tp to Event", + xtag("ev_tp.type", ev_tp.td()->canonical_name()), + xtag("Event", reflect::type_name()))); + } + } /*insert_tp*/ + + // ----- Inherited from AbstractSink ----- + + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + virtual bool allow_volatile_source() const override { return false; } + virtual void notify_ev(Event const & ev) override { + ++(this->n_in_ev_); + this->insert(ev); + } + + // ----- Inherited from AbstractSource ----- + + virtual void display(std::ostream & os) const override { + using xo::xtag; + + os << "name()) + << xtag("n_in_ev", this->n_in_ev()) + << ">"; + } /*display*/ + + // ----- Inherited from AbstractEventProcessor ----- + + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { name_ = x; } + + private: + EventStoreImpl() = default; + + template + std::uint32_t visit_range(typename EventTree::const_iterator lo_ix, + typename EventTree::const_iterator hi_ix, + Fn && fn) const { + std::uint32_t n = 0; + for (; lo_ix != hi_ix; ++lo_ix, ++n) { + fn(lo_ix->second); + } + + return n; + } /*visit_range*/ + + private: + /* reporting name for this store */ + std::string name_; + /* fetches per-event timestamp */ + EventTimeFn event_tm_fn_; + /* counts lifetime #of incoming events (see .notify_ev()) */ + uint32_t n_in_ev_ = 0; + /* events stored here */ + EventTree tree_; + }; /*EventStoreImpl*/ + + template + using StructEventStore = EventStoreImpl>; + + template + using PtrEventStore = EventStoreImpl>; + + /* Require: + * EventTimeConcept> + */ + template + class SinkToEventStore : public SinkEndpoint { + public: + using EventStore = StructEventStore; + + public: + SinkToEventStore() = default; + + virtual void notify_ev(T const & ev) override { + store_.insert(ev); + } /*notify_ev*/ + + private: + /* stash remembered events (all of them!) here */ + EventStore store_; + }; /*SinkToEventStore*/ + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end EventStore.hpp */ diff --git a/include/xo/reactor/EventTimeFn.hpp b/include/xo/reactor/EventTimeFn.hpp new file mode 100644 index 00000000..28c36d0b --- /dev/null +++ b/include/xo/reactor/EventTimeFn.hpp @@ -0,0 +1,38 @@ +/* @file EventTimeFn.hpp */ + +#pragma once + +#include "time/Time.hpp" +#include + +namespace xo { + namespace reactor { + template + concept EventTimeConcept = requires(EventTimeFn etfn, Event ev) { + { etfn(ev) } -> std::same_as; + }; + + template + class StructEventTimeFn { + public: + using event_t = Event; + using utc_nanos = xo::time::utc_nanos; + + public: + utc_nanos operator()(Event const & ev) const { return ev.tm(); } + }; /*StructEventTimeFn*/ + + template + class PtrEventTimeFn { + public: + using event_t = Event; + using utc_nanos = xo::time::utc_nanos; + + public: + utc_nanos operator()(Event const & ev) const { return ev->tm(); } + }; /*PtrEventTimeFn*/ + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end EventTimeFn.hpp */ diff --git a/include/xo/reactor/HeapReducer.hpp b/include/xo/reactor/HeapReducer.hpp new file mode 100644 index 00000000..5355e056 --- /dev/null +++ b/include/xo/reactor/HeapReducer.hpp @@ -0,0 +1,72 @@ +/* @file HeapReducer.hpp */ + +#pragma once + +#include "reactor/Reducer.hpp" + +namespace xo { + namespace reactor { + /* collect incoming events in a heap, + * ordered by timestamp. + * output events in increasing timestamp order. + * Information preserving in all other respects + * + * Require: + * - Event is null-constructible + * - Event is copyable + * - EventTimeFn :: Event -> utc_nanos + */ + template> + class HeapReducer : public ReducerBase { + public: + using utc_nanos = xo::time::utc_nanos; + public: + HeapReducer() = default; + HeapReducer(EventTimeFn const & evtfn) : ReducerBase(evtfn) {} + + bool is_empty() const { return this->event_heap_.empty(); } + /* require: .is_empty() = false */ + utc_nanos next_tm() const { return this->event_tm(this->event_heap_.front()); } + /* #of events stored in this reducer */ + uint32_t n_event() const { return this->event_heap_.size(); } + + Event const & last_annexed_ev() const { return this->annexed_ev_; } + + void include_event(Event const & ev) { + this->event_heap_.push_back(ev); + std::push_heap(this->event_heap_.begin(), + this->event_heap_.end(), + std::greater()); + } /*include_event*/ + + void include_event(Event && ev) { + this->event_heap_.push_back(std::move(ev)); + std::push_heap(this->event_heap_.begin(), + this->event_heap_.end(), + std::greater()); + } /*include_event*/ + + Event & annex_one() { + this->annexed_ev_ = this->event_heap_.front(); + std::pop_heap(this->event_heap_.begin(), + this->event_heap_.end(), + std::greater()); + this->event_heap_.pop_back(); + + return this->annexed_ev_; + } /*annex_one*/ + + // ----- Inherited from ReducerBase ----- + + // utc_nanos event_tm(Event const & x); + + private: + /* queued Events, in increasing timestamp order */ + std::vector event_heap_; + /* annexed event, removed from .event_heap */ + Event annexed_ev_; + }; /*HeapReducer*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end HeapReducer.hpp */ diff --git a/include/xo/reactor/LastReducer.hpp b/include/xo/reactor/LastReducer.hpp new file mode 100644 index 00000000..86a5913c --- /dev/null +++ b/include/xo/reactor/LastReducer.hpp @@ -0,0 +1,154 @@ +/* @file LastReducer.hpp */ + +#pragma once + +#include "reactor/Reducer.hpp" +#include + +namespace xo { + namespace reactor { + /* implementation record used in LastReducer. + * LastReducer (see below) remembers a single event, + * + will be updated on successive calls to + * LastReducer.include_event() + * + * need to remember the _first_ (& therefore earliest) + * event timestamp in such a wave, since that establishes when simulator + * should deliver the event -- even if event is subsequently + * overwritten. + * + * once event is delivered, timestamp can reset + * + * otherwise if upstream producer sends events with + * future timestamps, can get indefinite postponement + * with simulation clock failing to catch up to event time. + * + */ + + template + class EventRecd { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + EventRecd() = default; + EventRecd(utc_nanos tm, Event ev) : trigger_tm_{tm}, ev_{ev} {} + EventRecd(utc_nanos tm, Event && ev) : trigger_tm_{tm}, ev_{std::move(ev)} {} + + public: + /* if sim, deliver event when simulation clock reaches + * .trigger_tm; .trigger_tm can be earlier than .ev time + */ + utc_nanos trigger_tm_; + /* event to deliver */ + Event ev_; + }; + + /* reducer that just remembers the last event + * + * Require: + * - Event is null-contructible + * - Event is copyable + * + * LastReducer provides reentrancy support. This support doesn't operate + * if Event copy is not deep, e.g. for Event = rpn + * + * .include_event() + * /-------\ -----------------> /------\ + * | empty | | full | + * \-------/ <----------------- \------/ + * . .annex_one() . + * . . + * .is_empty()=true .is_empty()=false + */ + template> + class LastReducer : public ReducerBase { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + LastReducer() = default; + LastReducer(EventTimeFn const & evtfn) : ReducerBase(evtfn) {} + + bool is_empty() const { return empty_flag_; } + /* require: .is_empty() = false */ + utc_nanos next_tm() const { + return this->last_ev_[this->last_ix_].trigger_tm_; + //return this->event_tm(this->last_ev_[this->last_ix_]); + } + /* #of events stored in this reducer (0 or 1) */ + uint32_t n_event() const { return this->empty_flag_ ? 0 : 1; } + + Event const & last_annexed_ev() const { + return this->last_ev_[1 - this->last_ix_].ev_; + } + + EventRecd & include_event_aux(Event const & ev) { + EventRecd & evr + = this->last_ev_[this->last_ix_]; + + if (this->empty_flag_) { + /* evr.trigger_tm will be preserved across + * successive calls to .include_event(); + * until .annex_one() + */ + evr.trigger_tm_ = this->event_tm(ev); + + this->empty_flag_ = false; + } + + return evr; + } /*include_event_aux*/ + + void include_event(Event const & ev) { + EventRecd & evr + = this->include_event_aux(ev); + + evr.ev_ = ev; + } /*include_event*/ + + void include_event(Event && ev) { + EventRecd & evr + = this->include_event_aux(ev); + + evr.ev_ = std::move(ev); + } /*include_event*/ + + Event & annex_one() { + std::uint32_t annexed_ix = this->last_ix_; + + /* since .empty_flag is true, + * next call to .include_event_aux() will + * capture new timestamp + */ + this->empty_flag_ = true; + this->last_ix_ = (1 - this->last_ix_); + + return this->last_ev_[annexed_ix].ev_; + } /*annex_one*/ + + // ----- Inherited from ReducerBase ----- + + //utc_nanos event_tm(Event const & ev) const { return this->event_tm_fn_(ev); } + + private: + /* true when reducer contains 0 queued events, + * not counting any annexed event + */ + bool empty_flag_ = true; + + /* .last_ev[.last_ix] updated by .include_event() + */ + std::uint32_t last_ix_ = 0; + /* remember two events + * (a) a single queued event (updated by .include_event()) + * (b) a single removed event (reported by .annex_one()) + * + * roles of .last_ev[0], .last_ev[1] reverse each time .annex_one() runs + */ + std::array, 2> last_ev_; + }; /*LastReducer*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end LastReducer.hpp */ diff --git a/include/xo/reactor/PollingReactor.hpp b/include/xo/reactor/PollingReactor.hpp new file mode 100644 index 00000000..7dbad4ab --- /dev/null +++ b/include/xo/reactor/PollingReactor.hpp @@ -0,0 +1,44 @@ +/* @file PollingReactor.hpp */ + +#pragma once + +#include "Reactor.hpp" +#include "ReactorSource.hpp" +#include +#include + +namespace xo { + namespace reactor { + /* reactor that runs by polling an ordered set of sources */ + class PollingReactor : public Reactor { + public: + PollingReactor() = default; + + // ----- inherited from Reactor ----- + + virtual bool add_source(ref::brw src) override; + virtual bool remove_source(ref::brw src) override; + virtual std::uint64_t run_one() override; + + private: + /* find non-empty source, starting from .source_v_[start_ix], + * wrapping around to .source_v_[start_ix - 1]. + * + * return index of first available non-empty source, + * or -1 if all sources are empty + */ + std::int64_t find_nonempty_source(std::size_t start_ix); + + private: + /* next source to poll will be .source_v_[.next_ix_] */ + std::size_t next_ix_ = 0; + + /* ordered set of sources (see reactor::Source) + * reactor will poll sources in round-robin order + */ + std::vector source_v_; + }; /*PollingReactor*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end PollingReactor.hpp */ diff --git a/include/xo/reactor/PolyAdapterSink.hpp b/include/xo/reactor/PolyAdapterSink.hpp new file mode 100644 index 00000000..e8f68eb9 --- /dev/null +++ b/include/xo/reactor/PolyAdapterSink.hpp @@ -0,0 +1,92 @@ +/* file PolyAdapterSink.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "Sink.hpp" +#include "xo/reflect/Reflect.hpp" + +namespace xo { + namespace reactor { + /* adapter between a source that delivers a particular event type T, + * and a sink that accepts arbitrarily-typed events via .notify_ev_tp() + * Use this to connect to a polymorphic sink. + * + * Require: + * - .poly_sink.allow_polymorphic_source() + * (ofc. otherwise no point in using PolyAdapterSink) + * - .poly_sink.allow_volatile_source() + * need this bc will be wrapping event with TaggedPtr, + * which doesn't manage event lifetime + */ + template + class PolyAdapterSink : public reactor::Sink1 { + public: + using Reflect = reflect::Reflect; + using TaggedPtr = reflect::TaggedPtr; + + public: + /* named ctor idiom */ + static ref::rp make(ref::rp poly_sink) { + //xo::scope lscope("PolyAdapterSink::make"); + + ref::rp retval(new PolyAdapterSink(poly_sink)); + + //lscope.log("adapter", (void*)retval.get()); + + return retval; + } /*make*/ + + // ----- Inherited from Sink1 ----- + + virtual void notify_ev(T const & ev) override { + //xo::scope lscope("PolyAdapterSink::notify_ev"); + //lscope.log(xo::xtag("ev", ev)); + + TaggedPtr ev_tp = Reflect::make_tp(const_cast(&ev)); + + this->notify_ev_tp(ev_tp); + } /*notify_ev*/ + + // ----- Inherited from AbstractSink ----- + + virtual bool allow_volatile_source() const override { return true; } + virtual uint32_t n_in_ev() const override { return this->poly_sink_->n_in_ev(); } + /* note: ok to do this, however if expecting to use this entry point, + * maybe don't need to interpose PolyAdapterSink ahead of .poly_sink + */ + virtual void notify_ev_tp(TaggedPtr const & ev_tp) override { + //xo::scope lscope("PolyAdapterSink::notify_ev_tp"); + + return this->poly_sink_->notify_ev_tp(ev_tp); + } + + // ----- Inherited from AbstractEventProcessor ----- + + virtual std::string const & name() const override { return this->poly_sink_->name(); } + virtual void set_name(std::string const & x) override { this->poly_sink_->set_name(x); } + virtual void visit_direct_consumers(std::function ep)> const & fn) override { + this->poly_sink_->visit_direct_consumers(fn); + } + virtual void display(std::ostream & os) const override { + using xo::xtag; + os << "()) + << xtag("poly", this->poly_sink_) + << ">"; + } /*display*/ + + private: + PolyAdapterSink(ref::rp poly_sink) : poly_sink_{std::move(poly_sink)} {} + + private: + /* mandate: .poly_sink.allow_polymorphic_source() is true */ + ref::rp poly_sink_; + }; /*PolyAdapterSink*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end PolyAdapterSink.hpp */ diff --git a/include/xo/reactor/Reactor.hpp b/include/xo/reactor/Reactor.hpp new file mode 100644 index 00000000..c56a590c --- /dev/null +++ b/include/xo/reactor/Reactor.hpp @@ -0,0 +1,63 @@ +/* @file Reactor.hpp */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include + +namespace xo { + namespace reactor { + class ReactorSource; + + /* abtract api for a reactor: + * something that arranges to have work done on a set of Sources. + */ + class Reactor : public ref::Refcount { + public: + virtual ~Reactor() = default; + + /* add source src to this reactor. + * on success, invoke src.notify_reactor_add(this) + * + * returns true if source added; false if already present + */ + virtual bool add_source(ref::brw src) = 0; + + /* remove source src from this reactor. + * source must previously have been added by + * .add_source(src). + * + * on success, invoke src.notify_reactor_remove(this) + * + * returns true if source removed; false if not present + */ + virtual bool remove_source(ref::brw src) = 0; + + /* notification when non-primed source (source with no known events) + * becomes primed (source with at least one event) + */ + virtual void notify_source_primed(ref::brw src) = 0; + + /* dispatch one reactor event, borrowing the calling thread + * amount of work this represents is Source/Sink specific. + * + * returns #of events dispatched (0 or 1) + */ + virtual std::uint64_t run_one() = 0; + + /* borrow calling thread to dispatch reactor events. + * if n is -1, run indefinitely + * otherwise dispatch up to n events. + * n = 0 is a noop + */ + void run_n(int32_t n); + + /* borrow calling thread to run indefinitely. + * suitable implementation for dedicated reactor threads + */ + void run() { this->run_n(-1); } + }; /*Reactor*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end Reactor.hpp */ diff --git a/include/xo/reactor/ReactorSource.hpp b/include/xo/reactor/ReactorSource.hpp new file mode 100644 index 00000000..92f275d4 --- /dev/null +++ b/include/xo/reactor/ReactorSource.hpp @@ -0,0 +1,130 @@ +/* @file ReactorSource.hpp */ + +#pragma once + +#include "AbstractSource.hpp" +//#include "time/Time.hpp" +#include + +namespace xo { + namespace reactor { + class Reactor; + + /* abstract api for a source of events. + * Event representation is left open: Sources and Sinks + * need to have compatible event representations, + * and coordination is left to such (Source, Sink) pairs. + * + * Source->Sink activity may be expected to be mediated by a reactor, + * that implements the Reactor api. + * + * At any time, A Source can be associated with at most one reactor. + * Sources are informed of Reactor<->Source association being + * formed/broken by the + * .notify_reactor_add(), .notify_reactor_remove() + * methods + * + * The source api intends also to provide for simulation. + * There introduces two simulation-specific methods: + * .sim_current_tm() + * .sim_advance_until() + * + * A non-simulation source can implement these as calls to + * .online_current_tm(), .online_advance_until() respectively + * .online_current_tm() aborts since an online source is never exhausted + * .online_advance_until() is a no-op that returns 0 + * + * Loop for consuming from a primary simulation source: + * + * brw s = ...; + * while(!s->is_exhausted()) + * s->deliver_one(); + * + * Secondary sources (sources that depend on other sources) can be + * in a state where they don't know their next event, in which case: + * + * s->is_notprimed() == true + */ + class ReactorSource : public AbstractSource { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + virtual ~ReactorSource() = default; + + /* true if source is currently empty (has 0 events to deliver) */ + virtual bool is_empty() const = 0; + bool is_nonempty() const { return !this->is_empty(); } + + /* true when source knows its next event + * A source that isn't primed is also excluded from simulation + * heap until it becomes primed. + * This make feasible simulation sources that + * depend on other simulation sources + */ + virtual bool is_primed() const { return !this->is_empty(); } + virtual bool is_notprimed() const { return this->is_empty(); } + + /* if true, this source has no events, and will never publish more events + * - for sim, return true for a standalone source that has replayed all events + * - for rt, set during orderly + */ + virtual bool is_exhausted() const = 0; + + /* if this is a simulation source and .is_exhausted is false: + * returns next event time; more precisely, no events exist prior to + * this time. + * + * if sim, and .is_primed = true, + * returns timestamp of next event + */ + virtual utc_nanos sim_current_tm() const = 0; + + /* promise: + * - .current_tm() > tm || .is_notprimed() || .is_exhausted() = true + * - if replay_flag is true, then any events between previous .current_tm() + * and new .current_tm() will have been published + * + * returns #of events delivered. + * does not count events that were skipped, so always returns 0 if + * replay_flag is false + */ + virtual std::uint64_t sim_advance_until(utc_nanos tm, bool replay_flag) = 0; + + /* informs source when it's added to a reactor + + * (see Reactor.add_source()) + */ + virtual void notify_reactor_add(Reactor * /*reactor*/) {} + + /* informs source when it's removed from a reactor + * (see Reactor.remove_source()) + */ + virtual void notify_reactor_remove(Reactor * /*reactor*/) {} + + // ----- Inherited from AbstractSource ----- + + /* deliver one event to attached sink + * interpretation of 'one event' is source-specific; + * could be a collapsed or batched event in practice. + * + * no-op if source is empty. + * + * if sim, promise: + * - new .current_tm >= old .current_tm() || .is_notprimed() || .is_exhausted() + * + * returns #of events delivered. Must be 0 or 1 in this context + */ + virtual std::uint64_t deliver_one() override = 0; + + protected: + /* default implementations for online sources */ + utc_nanos online_current_tm() const; + uint64_t online_advance_until(utc_nanos tm, bool replay_flag); + }; /*ReactorSource*/ + + using ReactorSourcePtr = ref::rp; + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end ReactorSource.hpp */ diff --git a/include/xo/reactor/Reducer.hpp b/include/xo/reactor/Reducer.hpp new file mode 100644 index 00000000..907d9d04 --- /dev/null +++ b/include/xo/reactor/Reducer.hpp @@ -0,0 +1,33 @@ +/* @file Reducer.hpp */ + +#pragma once + +#include "reactor/EventTimeFn.hpp" + +namespace xo { + namespace reactor { + /* LastReducer, HeapReducer inherit ReducerBase */ + template + class ReducerBase { + static_assert(EventTimeConcept); + + public: + using utc_nanos = xo::time::utc_nanos; + + public: + ReducerBase() = default; + ReducerBase(EventTimeFn const & evtfn) : event_tm_fn_{evtfn} {} + + utc_nanos event_tm(Event const & ev) const { return this->event_tm_fn_(ev); } + + private: + /* Event ev = ...; + * .event_tm_fn(ev) -> utc_nanos + * reports event time associated with ev + */ + EventTimeFn event_tm_fn_; + }; /*ReducerBase*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end Reducer.hpp */ diff --git a/include/xo/reactor/SecondarySource.hpp b/include/xo/reactor/SecondarySource.hpp new file mode 100644 index 00000000..398a17bb --- /dev/null +++ b/include/xo/reactor/SecondarySource.hpp @@ -0,0 +1,359 @@ +/* @file SecondarySource.hpp */ + +#pragma once + +#include "time/Time.hpp" +#include "reactor/Sink.hpp" +#include "reactor/DirectSource.hpp" +#include "reactor/Reactor.hpp" +#include "callback/CallbackSet.hpp" +#include "reflect/demangle.hpp" +#include + +namespace xo { + namespace reactor { + /* A passive event source. + * Can use as backend publisher when implementating another + * event processor. + */ + template> + class SecondarySource : public EventSource> { + public: + using EventSink = Sink1; + template + using RpCallbackSet = fn::RpCallbackSet; + using CallbackId = fn::CallbackId; + using TypeDescr = xo::reflect::TypeDescr; + using utc_nanos = xo::time::utc_nanos; + + public: + ~SecondarySource() = default; + + static ref::rp make() { return new SecondarySource(); } + + /* last event delivered from this source -- + * i.e. event in most recent call to .deliver_one_aux() + */ + Event const & last_annexed_ev() const { return this->reducer_.last_annexed_ev(); } + + void notify_upstream_exhausted() { this->upstream_exhausted_ = true; } + + /* make event available to reactor, by adding to internal reducer */ + void notify_secondary_event(Event const & ev) { + /* test if ev is priming, update .current_tm */ + bool is_priming = this->preprocess_secondary_event(ev); + + this->reducer_.include_event(ev); + + this->postprocess_secondary_event(is_priming); + } /*notify_secondary_event*/ + + void notify_secondary_event(Event && ev) { + bool is_priming = this->preprocess_secondary_event(ev); + + this->reducer_.include_event(ev); + + this->postprocess_secondary_event(is_priming); + } /*notify_secondary_event*/ + + template + void notify_secondary_event_v(T const & v) { + using xo::scope; + using xo::xtag; + + if (v.empty()) + return; + + scope log(XO_DEBUG(this->debug_sim_flag_)); + + log && log(xtag("name", this->name())); + + if (this->upstream_exhausted_) { + throw std::runtime_error("SecondarySource::notify_secondary_event_v" + ": not allowed after upstream exhausted"); + } + + uint32_t n_ev = 0; + + for (Event const & ev : v) { + utc_nanos evtm = this->reducer_.event_tm(ev); + + if (this->current_tm_ < evtm) + this->current_tm_ = evtm; + + ++n_ev; + } + + log && log(xtag("T", reflect::type_name()), + xtag("n_ev", n_ev)); + + if (n_ev > 0) { + /* if reducer is empty when .notify_secondary_event_v() begins, + * then reactor/simulator needs to be notified that source is no longer empty + */ + bool is_priming = this->reducer_.is_empty(); + + for (Event const & ev : v) + this->reducer_.include_event(ev); + + Reactor * reactor = this->parent_reactor_; + + if (reactor) { + if (is_priming) { + /* reactor/simulator takes responsibility for delivering events */ + reactor->notify_source_primed(ref::brw::from_native(this)); + } + } else { + /* special case if no reactor: deliver immediately */ + + //this->deliver_one(); + this->deliver_all(); + } + } + } /*notify_secondary_event_v*/ + + // ----- inherited from EventSource ----- + + CallbackId add_callback(ref::rp const & cb) override { + return this->cb_set_.add_callback(cb); + } /*add_callback*/ + + void remove_callback(CallbackId id) override { + this->cb_set_.remove_callback(id); + } /*remove_callback*/ + + // ----- inherited from ReactorSource ----- + + virtual bool is_empty() const override { return this->reducer_.is_empty(); } + virtual bool is_exhausted() const override { return this->upstream_exhausted_ && this->is_empty(); } + + virtual utc_nanos sim_current_tm() const override { + using xo::scope; + using xo::xtag; + + if (this->reducer_.is_empty()) { + /* this is a tricky case. + * it means this source doesn't + * _know_ specific next event yet; however new events + * may appear at any time by way of .notify_event() + * + * If event doesn't know next event, then .current_tm isn't useful + * for establishing priority relative to other sources. + * rely on priming mechanism instead, + * which means that control should never come here. + */ + return this->current_tm_; + } else { + scope log(XO_DEBUG(false /*this->debug_sim_flag_*/), + xtag("name", this->name_), + xtag("next_tm", this->reducer_.next_tm())); + + return this->reducer_.next_tm(); + } + } /*sim_current_tm*/ + + virtual std::uint64_t deliver_one() override { + return this->deliver_one_aux(true /*replay_flag*/); + } + + virtual std::uint64_t sim_advance_until(utc_nanos target_tm, + bool replay_flag) override + { + uint64_t retval = 0; + + while (!this->reducer_.is_empty()) { + utc_nanos tm = this->sim_current_tm(); + + if (tm < target_tm) { + retval += this->deliver_one_aux(replay_flag); + } else { + break; + } + } + + return retval; + } /*sim_advance_until*/ + + virtual void notify_reactor_add(Reactor * reactor) override { + assert(!this->parent_reactor_); + + this->parent_reactor_ = reactor; + } /*notify_reactor_add*/ + + virtual void notify_reactor_remove(Reactor * /*reactor*/) override {} + + // ----- inherited from AbstractSource ----- + + virtual TypeDescr source_ev_type() const override { + return reflect::Reflect::require(); + } /*source_ev_type*/ + + virtual uint32_t n_out_ev() const override { return n_out_ev_; } + /* #of events queued for delivery */ + virtual uint32_t n_queued_out_ev() const override { return this->reducer_.n_event(); } + + virtual bool debug_sim_flag() const override { return debug_sim_flag_; } + virtual void set_debug_sim_flag(bool x) override { this->debug_sim_flag_ = x; } + + virtual CallbackId attach_sink(ref::rp const & sink) override { + ref::rp native_sink + = EventSink::require_native("SecondarySource::attach_sink", sink); + + if (native_sink) { + if (!this->is_volatile() + || native_sink->allow_volatile_source()) + { + return this->add_callback(native_sink); + } else { + throw std::runtime_error("SecondarySource::attach_sink" + ": sink requires non-volatile source " + + std::string(reflect::type_name())); + } + } else { + throw std::runtime_error("SecondarySource::attach_sink" + ": expected sink accepting " + + std::string(reflect::type_name())); + } + } /*attach_sink*/ + + virtual void detach_sink(CallbackId id) override { + this->remove_callback(id); + } /*detach_sink*/ + + // ----- Inherited from AbstractEventProcessor ----- + + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { this->name_ = x; } + + virtual void visit_direct_consumers(std::function ep)> const & fn) override { + + for(auto x : this->cb_set_) + fn(x.fn_.borrow()); + } /*visit_direct_consumers*/ + + private: + /* event book-keeping on receiving an event. + */ + bool preprocess_secondary_event(Event const & ev) + { + if (this->upstream_exhausted_) { + throw std::runtime_error("SecondarySource::notify_secondary_event" + ": not allowed after upstream exhausted"); + } + + utc_nanos evtm = this->reducer_.event_tm(ev); + + if (this->current_tm_ < evtm) + this->current_tm_ = evtm; + + /* if reducer is empty when .notify_event() begins, + * then reactor/simulator needs to be notified that source is no longer empty + */ + bool is_priming = this->reducer_.is_empty(); + + return is_priming; + } /*preprocess_secondary_event*/ + + /* event bookkeeping after receiving an event. + * + * Require: event has been propagated to .reducer + * + * is_priming. true if event causes source to + * become non-empty --> must notify reactor + */ + void postprocess_secondary_event(bool is_priming) { + using xo::scope; + using xo::xtag; + + Reactor * reactor = this->parent_reactor_; + + scope log(XO_DEBUG(this->debug_sim_flag_), + xtag("name", name_), + xtag("reactor", (void*)reactor), + xtag("is_priming", is_priming)); + + if (reactor) { + if (is_priming) { + /* reactor/simulator takes responsibility for delivering events */ + reactor->notify_source_primed(ref::brw::from_native(this)); + } + } else { + /* if no reactor, deliver immediately */ + this->deliver_one(); + } + } /*postprocess_secondary_event*/ + + /* deliver one event from reducer; + * invoke callback whenever replay_flag is true + */ + std::uint64_t deliver_one_aux(bool replay_flag) { + scope log(XO_DEBUG(this->debug_sim_flag_), + xtag("name", this->name_), + xtag("reducer.empty", this->reducer_.is_empty()), + xtag("replay_flag", replay_flag)); + + if (this->reducer_.is_empty()) + return 0; + + /* need to remove event _before_ invoking callbacks; + * callbacks may indirectly call this->notify_secondary_event(), + * modifiying .reducer + * + * reducer may use double-buffering scheme or similar to + * mitigate copying, esp when Event objects are heavy + */ + Event & ev = this->reducer_.annex_one(); + + /* if SecondarySource: + * Event ev = this->event_heap_.front(); + * std::pop_heap(this->event_heap_.begin(), + * this->event_heap_.end(), + * std::greater()); + * this->event_heap_.pop_back(); + */ + + if (replay_flag) { + ++(this->n_out_ev_); + this->cb_set_.invoke(&EventSink::notify_ev, ev); + } + + return 1; + } /*deliver_one_aux*/ + + private: + /* current time for this source */ + utc_nanos current_tm_; + + /* reporting name for this source (use when .debug_sim_flag set) + */ + std::string name_; + + /* if true, reactor/simulator to log interaction with this source + */ + bool debug_sim_flag_ = false; + + /* count lifetime #of outgoing events */ + uint32_t n_out_ev_ = 0; + + /* set this to true, once, to announce that upstream will send + * no more events. see .notify_upstream_exhausted() + */ + bool upstream_exhausted_ = false; + + /* events to be delivered to callbacks. + * multiple events may be collapsed depending on Reducer implementation + */ + Reducer reducer_; + + /* reactor/simulator being used to schedule consumption. if ommitted, + * will borrow thread calling .notify_secondary_event() + */ + Reactor * parent_reactor_ = nullptr; + + /* invoke callbacks in this set to send an outgoing event */ + RpCallbackSet cb_set_; + }; /*SecondarySource*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end SecondarySource.hpp */ diff --git a/include/xo/reactor/Sink.hpp b/include/xo/reactor/Sink.hpp new file mode 100644 index 00000000..2ba561c7 --- /dev/null +++ b/include/xo/reactor/Sink.hpp @@ -0,0 +1,222 @@ +/* @file Sink.hpp */ + +#pragma once + +#include "AbstractSink.hpp" +#include "AbstractSource.hpp" +#include "PolyAdapterSink.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/indentlog/print/time.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/cxxutil/demangle.hpp" +#include + +namespace xo { + namespace reactor { + /* Sink for events of type T + * + * inheritance: + * ref::Refcount + * ^ + * isa + * | + * reactor::AbstractEventProcessor + * ^ + * isa + * | + * reactor::AbstractSink + * ^ + * isa + * | + * reactor::Sink1 + */ + template + class Sink1 : public AbstractSink { + public: + using Reflect = reflect::Reflect; + using TypeDescr = reflect::TypeDescr; + + public: + /* convenience: convert abstract sink to Sink1*, + * or throw + */ + static ref::rp> require_native(std::string_view caller, + ref::rp const & sink) + { + using xo::scope; + using xo::xtag; + + /* 1. if sink expects events of type T, + * make direct connection + */ + Sink1 * native_sink = nullptr; + + native_sink = dynamic_cast *>(sink.get()); + + if (native_sink) + return native_sink; + + /* 2. if sink is polymorphic, + * make type-erasing adapter + */ + + if (sink->allow_polymorphic_source()) { +#ifdef DEBUG_NOT_USING + scope lscope("Sink1::require_native: create PolyAdapterSink"); + lscope.log(xtag("caller", caller)); +#endif + + return PolyAdapterSink::make(sink); + } + + if (!native_sink) { +#ifdef DEBUG_EVENT_TYPEINFO + std::type_info const * sink_parent_typeinfo + = sink->parent_typeinfo(); +#endif + + std::size_t src_hashcode = typeid(T).hash_code(); + + throw std::runtime_error(tostr("Sink1::require_native" + ": wanted to sink S, but sink expects T", + xtag("caller", caller), + xtag("T", sink->sink_ev_type()->canonical_name()), + xtag("S", reflect::type_name()), + xtag("required_hashcode", typeid(Sink1).hash_code()), + xtag("required_name", typeid(Sink1).name()), + xtag("src_hashcode", src_hashcode), + xtag("sink_hashcode", sink->sink_ev_type()->typeinfo()->hash_code()) +#ifdef DEBUG_EVENT_TYPEINFO + , xtag("sink_hashcode", sink->item_typeinfo()->hash_code()) + , xtag("sink_parent_hashcode", sink_parent_typeinfo->hash_code()) + , xtag("sink_parent_name", sink_parent_typeinfo->name()) + , xtag("sink.type", sink->self_typename()) + , xtag("sink.parent_type", sink->parent_typename()) +#endif + )); + } + + return native_sink; + } /*require_native*/ + + virtual TypeDescr sink_ev_type() const override { return reflect::Reflect::require(); } + /* accept incoming event */ + virtual void notify_ev(T const & ev) = 0; + + /* invoke these when this sink added to, or removed from, a source */ + virtual void notify_add_callback() {} + virtual void notify_remove_callback() {} + + // ----- inherited from AbstractSink ----- + + /* Sink1 only allows source providing T */ + virtual bool allow_polymorphic_source() const override { return false; } + + virtual void attach_source(ref::rp const & src) override { + src->attach_sink(this); + } /*attach_source*/ + + virtual void notify_ev_tp(TaggedPtr const & ev_tp) override { + using xo::xtag; + + T * p_ev = ev_tp.recover_native(); + + if (p_ev) { + this->notify_ev(*p_ev); + } else { + throw std::runtime_error(tostr("Sink1::notify_ev_tp" + ": unable to convert ev_tp to T", + xtag("ev_tp.type", ev_tp.td()->canonical_name()), + xtag("T", reflect::type_name()))); + } + } /*notify_ev_tp*/ + }; /*Sink1*/ + + /* a sink with no further downstream processors */ + template + class SinkEndpoint : public Sink1 { + public: + // ----- Inherited from AbstractEventProcessor ----- + + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { name_ = x; } + + virtual void visit_direct_consumers(std::function)> const &) override { + /* *this is not an event source */ + } /*visit_direct_consumers*/ + + private: + /* reporting name for this sink */ + std::string name_; + }; /*SinkEndpoint*/ + + template + class SinkToFunction : public SinkEndpoint { + public: + SinkToFunction(Fn fn) : fn_{std::move(fn)} {} + + /* NOTE: conservative choice here, could templatize on this */ + virtual bool allow_volatile_source() const override { return false; } + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + virtual void notify_ev(T const & ev) override { + ++(this->n_in_ev_); + fn_(ev); + } /*notify_ev*/ + + virtual void display(std::ostream & os) const override { + using xo::xtag; + + os << "name()) + << xtag("n_in_ev", this->n_in_ev()) + << ">"; + } /*display*/ + + private: + Fn fn_; + /* counts lifetime #of incoming events (see .notify_ev()) */ + uint32_t n_in_ev_ = 0; + }; /*SinkToFunction*/ + + /* sink that prints to console */ + template + class SinkToConsole : public SinkEndpoint { + public: + SinkToConsole() {} + + virtual bool allow_volatile_source() const override { return true; } + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + virtual void notify_ev(T const & ev) override { + //using logutil::operator<<; + + ++(this->n_in_ev_); + + std::cout << ev << std::endl; + } /*notify_ev*/ + + virtual void display(std::ostream & os) const override { + using xo::xtag; + + os << "name()) + << xtag("n_in_ev", this->n_in_ev()) + << ">"; + } /*display*/ + + private: + /* reporting name for this sink */ + std::string name_; + /* counts lifetime #of incoming events (see .notify_ev()) */ + uint32_t n_in_ev_ = 0; + }; /*SinkToConsole*/ + +#ifdef NOT_USING + class TemporaryTest { + public: + static ref::rp>> realization_printer(); + }; /*TemporaryTest*/ +#endif + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end Sink.hpp */ diff --git a/include/xo/reactor/init_reactor.hpp b/include/xo/reactor/init_reactor.hpp new file mode 100644 index 00000000..5977d7ad --- /dev/null +++ b/include/xo/reactor/init_reactor.hpp @@ -0,0 +1,20 @@ +/* file init_reactor.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + enum S_reactor_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + +/* end init_reactor.hpp */ diff --git a/src/reactor/AbstractEventProcessor.cpp b/src/reactor/AbstractEventProcessor.cpp new file mode 100644 index 00000000..e49cc374 --- /dev/null +++ b/src/reactor/AbstractEventProcessor.cpp @@ -0,0 +1,93 @@ +/* @file AbstractEventProcessor.cp */ + +#include "AbstractEventProcessor.hpp" +#include "xo/indentlog/print/tostr.hpp" +#include +#include + +namespace xo { + using ref::rp; + using ref::brw; + using xo::tostr; + using std::uint32_t; + + namespace reactor { + namespace { + /* search all event processors ep reachable (dowstream) from x, + * add to *m; + */ + void + map_network_helper(brw x, + uint32_t * tsort_ix, + std::unordered_map * m) + { + if (m->contains(x.get())) + return; + + auto fn = [tsort_ix, m] + (brw ep) + { + map_network_helper(ep, tsort_ix, m); + }; + + x->visit_direct_consumers(fn); + + /* postorder! */ + (*m)[x.get()] = ++(*tsort_ix); + + } /*map_network_helper*/ + } /*namespace*/ + + std::vector> + AbstractEventProcessor::map_network(rp const & x) + { + std::unordered_map network_map; + + /* index event processors in reverse topological order: + * if B is (directly or indirectly) downstream from A, + * then tsort_ix(B) < tsort_ix(A) + */ + uint32_t tsort_ix = 0; + + /* depth-first traversal, detect and short-circuit on dup paths */ + map_network_helper(x.borrow(), &tsort_ix, &network_map); + + /* invariant: tsort_ix = #of event processors in network */ + uint32_t n = tsort_ix; + + /* network_map, now in a topologically sorted order */ + std::map tsorted_map; + { + for(auto const & x : network_map) { + uint32_t tsort_ix = x.second; + AbstractEventProcessor * ep = x.first; + + tsorted_map[n - tsort_ix] = ep; + } + } + + std::vector> retval; + { + for(auto const & x : tsorted_map) + retval.push_back(x.second); + } + + return retval; + } /*map_network*/ + + void + AbstractEventProcessor::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + AbstractEventProcessor::display_string() const + { + return tostr(*this); + } /*display_string*/ + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end AbstractEventProcessor.cpp */ diff --git a/src/reactor/AbstractSource.cpp b/src/reactor/AbstractSource.cpp new file mode 100644 index 00000000..973ec4c6 --- /dev/null +++ b/src/reactor/AbstractSource.cpp @@ -0,0 +1,84 @@ +/* @file AbstractSource.cpp */ + +#include "AbstractSource.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/webutil/StreamEndpointDescr.hpp" +//#include "indentlog/scope.hpp" + +namespace xo { + using xo::web::StreamEndpointDescr; + using xo::reactor::AbstractSink; + using xo::ref::rp; + //using xo::scope; + //using xo::tostr; + + namespace reactor { + StreamEndpointDescr + AbstractSource::stream_endpoint_descr(std::string const & url_prefix) + { + auto subscribe_fn + = ([this] + (rp const & ws_sink) + { + //scope lscope("AbstractSource::stream_endpoint_descr.subscribe_fn"); + + /* ws_sink created by websocket, sends events to websocket as json + * see [websock/WebsocketSink] + */ + return this->attach_sink(ws_sink); + }); + + auto unsubscribe_fn + = ([this] + (CallbackId id) + { + this->detach_sink(id); + }); + + return StreamEndpointDescr(url_prefix, + subscribe_fn, + unsubscribe_fn); + } /*stream_endpoint_descr*/ + + uint64_t + AbstractSource::deliver_n(uint64_t n) + { + uint64_t retval = 0; + + for (uint64_t i=0; ideliver_one(); + + if (n1 == 0) { + /* short-circuit if source has less than n + * events available + */ + break; + } + + retval += n1; + } + + return retval; + } /*deliver_n*/ + + uint64_t + AbstractSource::deliver_all() + { + uint64_t retval = 0; + + for (;;) { + uint64_t n1 = this->deliver_one(); + + if (n1 == 0) + break; + + retval += n1; + } + + return retval; + } /*deliver_all*/ + + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end AbstractSource.cpp */ diff --git a/src/reactor/CMakeLists.txt b/src/reactor/CMakeLists.txt new file mode 100644 index 00000000..6e26ee50 --- /dev/null +++ b/src/reactor/CMakeLists.txt @@ -0,0 +1,21 @@ +# xo-reactor/src/reactor/CMakeLists.txt + +set(SELF_LIB reactor) +set(SELF_SRCS + AbstractEventProcessor.cpp AbstractSource.cpp ReactorSource.cpp + Sink.cpp + Reactor.cpp PollingReactor.cpp + init_reactor.cpp) + +xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies + +# note: changes to xo_dependency() calls here +# must coordinate with find_dependency() calls +# in xo-reactor/cmake/reactorConfig.cmake.in +# +xo_dependency(${SELF_LIB} reflect) +xo_dependency(${SELF_LIB} webutil) +xo_dependency(${SELF_LIB} callback) diff --git a/src/reactor/PollingReactor.cpp b/src/reactor/PollingReactor.cpp new file mode 100644 index 00000000..348ca739 --- /dev/null +++ b/src/reactor/PollingReactor.cpp @@ -0,0 +1,88 @@ +/* @file PollingReactor.cpp */ + +#include "PollingReactor.hpp" + +namespace xo { + using ref::brw; + using std::size_t; + using std::uint64_t; + using std::int64_t; + + namespace reactor { + bool + PollingReactor::add_source(brw src) + { + /* make sure src does not already appear in .source_v[] */ + for(ReactorSourcePtr const & x : this->source_v_) { + if(x.get() == src.get()) { + throw std::runtime_error("PollingReactor::add_source; source already present"); + return false; + } + } + + src->notify_reactor_add(this); + + this->source_v_.push_back(src.get()); + + return true; + } /*add_source*/ + + bool + PollingReactor::remove_source(brw src) + { + auto ix = std::find(this->source_v_.begin(), + this->source_v_.end(), + src); + + if(ix != this->source_v_.end()) { + src->notify_reactor_remove(this); + + this->source_v_.erase(ix); + + return true; + } + + return false; + } /*remove_source*/ + + int64_t + PollingReactor::find_nonempty_source(size_t start_ix) + { + size_t z = this->source_v_.size(); + + /* search sources [ix .. z) */ + for(size_t ix = start_ix; ix < z; ++ix) { + brw src = this->source_v_[ix]; + + if(src->is_nonempty()) + return ix; + } + + /* search source [0 .. ix) */ + for(size_t ix = 0, n = std::min(start_ix, z); ix < n; ++ix) { + brw src = this->source_v_[ix]; + + if(src->is_nonempty()) + return ix; + } + + return -1; + } /*find_nonempty_source*/ + + uint64_t + PollingReactor::run_one() + { + int64_t ix = this->find_nonempty_source(this->next_ix_); + + if(ix >= 0) { + brw src = this->source_v_[ix]; + + return src->deliver_one(); + } else { + return 0; + } + } /*run_one*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end PollingReactor.cpp */ diff --git a/src/reactor/Reactor.cpp b/src/reactor/Reactor.cpp new file mode 100644 index 00000000..2ef80e46 --- /dev/null +++ b/src/reactor/Reactor.cpp @@ -0,0 +1,26 @@ +/* file Reactor.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "Reactor.hpp" + +namespace xo { + namespace reactor { + void + Reactor::run_n(int32_t n) + { + if (n == -1) { + for (;;) { + this->run_one(); + } + } else { + for (int32_t i=0; irun_one(); + } + } + } /*run_n*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end Reactor.cpp */ diff --git a/src/reactor/ReactorSource.cpp b/src/reactor/ReactorSource.cpp new file mode 100644 index 00000000..0bb29397 --- /dev/null +++ b/src/reactor/ReactorSource.cpp @@ -0,0 +1,34 @@ +/* @file Source.cpp */ + +#include "ReactorSource.hpp" +#include "xo/indentlog/print/time.hpp" +#include + +namespace xo { + using xo::time::utc_nanos; + + namespace reactor { + utc_nanos + ReactorSource::online_current_tm() const + { + /* for an online source: + * .is_exhausted() must always be false; + * this implies that .sim_current_tm() should + * not be called in the first place + */ + + assert(false); + + return time::timeutil::epoch(); + } /*online_current_tm*/ + + std::uint64_t + ReactorSource::online_advance_until(utc_nanos /*tm*/, + bool /*replay_flag*/) + { + return 0; + } /*online_advance_until*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end Source.cpp */ diff --git a/src/reactor/Sink.cpp b/src/reactor/Sink.cpp new file mode 100644 index 00000000..1dbcdb62 --- /dev/null +++ b/src/reactor/Sink.cpp @@ -0,0 +1,18 @@ +/* @file Sink.cpp */ + +#include "Sink.hpp" +#include "xo/refcnt/Refcounted.hpp" + +namespace xo { + namespace reactor { +#ifdef NOT_USING + ref::rp>> + TemporaryTest::realization_printer() + { + return new SinkToConsole>(); + } /*realization_printer*/ +#endif + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end Sink.cpp */ diff --git a/src/reactor/init_reactor.cpp b/src/reactor/init_reactor.cpp new file mode 100644 index 00000000..b33e4775 --- /dev/null +++ b/src/reactor/init_reactor.cpp @@ -0,0 +1,31 @@ +/* file init_reactor.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "init_reactor.hpp" +#include "xo/reflect/init_reflect.hpp" + +namespace xo { + void + InitSubsys::init() + { + /* TODO: reflect reactor types */ + } /*init*/ + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* subsystem dependencies for reactor/ */ + retval ^= InitSubsys::require(); + + /* reactor/'s own initialization code */ + retval ^= Subsystem::provide("reactor", &init); + + return retval; + } /*require*/ +} /*namespace xo*/ + +/* end init_reactor.cpp */ From fe9b36140afa0abbc44c435cda254b7724bb230f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 15:21:36 -0400 Subject: [PATCH 0207/2524] + .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..41983345 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# lsp keep state here +.cache +# typical build directories +build +ccov From 43a08a9628ceb3df96e15a3f6eabbc691c3d85b5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 10 Oct 2023 15:22:31 -0400 Subject: [PATCH 0208/2524] gitignore: + compile_commands.json --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 41983345..8ea1f615 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ # typical build directories build ccov +# for lsp: manual symlink to chosen build directory +compile_commands.json From 0157f8dc04b056bc3dc241cc1eda42d0ecd779f9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 17:46:24 -0400 Subject: [PATCH 0209/2524] initial implementation --- include/xo/reactor/AbstractSource.hpp | 8 +- include/xo/reactor/DirectSource.hpp | 3 +- include/xo/reactor/EventSource.hpp | 30 +- include/xo/reactor/EventTimeFn.hpp | 14 +- include/xo/reactor/HeapReducer.hpp | 112 ++--- include/xo/reactor/PollingReactor.hpp | 56 +-- include/xo/reactor/PolyAdapterSink.hpp | 126 +++--- include/xo/reactor/Reactor.hpp | 98 ++-- include/xo/reactor/ReactorSource.hpp | 208 ++++----- include/xo/reactor/SecondarySource.hpp | 597 +++++++++++++------------ src/reactor/PollingReactor.cpp | 21 +- src/reactor/ReactorSource.cpp | 2 +- 12 files changed, 660 insertions(+), 615 deletions(-) diff --git a/include/xo/reactor/AbstractSource.hpp b/include/xo/reactor/AbstractSource.hpp index ed31d04f..4d995ab2 100644 --- a/include/xo/reactor/AbstractSource.hpp +++ b/include/xo/reactor/AbstractSource.hpp @@ -38,10 +38,16 @@ namespace xo { virtual TypeDescr source_ev_type() const = 0; /* if true: event objects (see .source_ev_type()) - * maybe overwritten between callbacks. + * may be overwritten between callbacks. * A sink that wants to capture events * (e.g. EventStore<>) will need to deep-copy them * if false: event objects are preserved between callbacks. + * + * A source that stores events received from elsewhere (e.g. FifoQueue) + * is probably volatile. + * + * A source that remembers (in explicit memory) every event it produces + * is not volatile */ virtual bool is_volatile() const = 0; diff --git a/include/xo/reactor/DirectSource.hpp b/include/xo/reactor/DirectSource.hpp index 03710281..7187e56d 100644 --- a/include/xo/reactor/DirectSource.hpp +++ b/include/xo/reactor/DirectSource.hpp @@ -2,7 +2,7 @@ #pragma once -#include "time/Time.hpp" +//#include "time/Time.hpp" #include "reactor/Sink.hpp" #include "reactor/EventSource.hpp" #include "reactor/HeapReducer.hpp" @@ -16,4 +16,3 @@ namespace xo { } /*namespace xo*/ /* end DirectSource.hpp */ - diff --git a/include/xo/reactor/EventSource.hpp b/include/xo/reactor/EventSource.hpp index a60297f7..055c7d6b 100644 --- a/include/xo/reactor/EventSource.hpp +++ b/include/xo/reactor/EventSource.hpp @@ -2,24 +2,24 @@ #pragma once -#include "reactor/ReactorSource.hpp" -#include "callback/CallbackSet.hpp" +#include "ReactorSource.hpp" +#include "xo/callback/CallbackSet.hpp" namespace xo { - namespace reactor { - template - class EventSource : public ReactorSource { - public: - using CallbackId = fn::CallbackId; + namespace reactor { + template + class EventSource : public ReactorSource { + public: + using CallbackId = fn::CallbackId; - public: - virtual CallbackId add_callback(ref::rp const & cb) = 0; - virtual void remove_callback(CallbackId id) = 0; - }; /*EventSource*/ - - } /*namespace reactor*/ + public: + virtual CallbackId add_callback(ref::rp const & cb) = 0; + virtual void remove_callback(CallbackId id) = 0; + }; /*EventSource*/ + + } /*namespace reactor*/ } /*namespace xo*/ /* end EventSource.hpp */ diff --git a/include/xo/reactor/EventTimeFn.hpp b/include/xo/reactor/EventTimeFn.hpp index 28c36d0b..ee206d11 100644 --- a/include/xo/reactor/EventTimeFn.hpp +++ b/include/xo/reactor/EventTimeFn.hpp @@ -2,7 +2,8 @@ #pragma once -#include "time/Time.hpp" +//#include "time/Time.hpp" +#include "xo/indentlog/timeutil/timeutil.hpp" #include namespace xo { @@ -17,7 +18,7 @@ namespace xo { public: using event_t = Event; using utc_nanos = xo::time::utc_nanos; - + public: utc_nanos operator()(Event const & ev) const { return ev.tm(); } }; /*StructEventTimeFn*/ @@ -32,6 +33,15 @@ namespace xo { utc_nanos operator()(Event const & ev) const { return ev->tm(); } }; /*PtrEventTimeFn*/ + template + class PairEventTimeFn { + public: + using utc_nanos = xo::time::utc_nanos; + using event_t = std::pair; + + public: + utc_nanos operator()(event_t const & ev) const { return ev.first; } + }; /*PairEventTimeFn*/ } /*namespace reactor*/ } /*namespace xo*/ diff --git a/include/xo/reactor/HeapReducer.hpp b/include/xo/reactor/HeapReducer.hpp index 5355e056..7a7456ae 100644 --- a/include/xo/reactor/HeapReducer.hpp +++ b/include/xo/reactor/HeapReducer.hpp @@ -1,72 +1,72 @@ /* @file HeapReducer.hpp */ -#pragma once +#pragma once -#include "reactor/Reducer.hpp" +#include "Reducer.hpp" namespace xo { - namespace reactor { - /* collect incoming events in a heap, - * ordered by timestamp. - * output events in increasing timestamp order. - * Information preserving in all other respects - * - * Require: - * - Event is null-constructible - * - Event is copyable - * - EventTimeFn :: Event -> utc_nanos - */ - template> - class HeapReducer : public ReducerBase { - public: - using utc_nanos = xo::time::utc_nanos; - public: - HeapReducer() = default; - HeapReducer(EventTimeFn const & evtfn) : ReducerBase(evtfn) {} + namespace reactor { + /* collect incoming events in a heap, + * ordered by timestamp. + * output events in increasing timestamp order. + * Information preserving in all other respects + * + * Require: + * - Event is null-constructible + * - Event is copyable + * - EventTimeFn :: Event -> utc_nanos + */ + template> + class HeapReducer : public ReducerBase { + public: + using utc_nanos = xo::time::utc_nanos; + public: + HeapReducer() = default; + HeapReducer(EventTimeFn const & evtfn) : ReducerBase(evtfn) {} - bool is_empty() const { return this->event_heap_.empty(); } - /* require: .is_empty() = false */ - utc_nanos next_tm() const { return this->event_tm(this->event_heap_.front()); } - /* #of events stored in this reducer */ - uint32_t n_event() const { return this->event_heap_.size(); } + bool is_empty() const { return this->event_heap_.empty(); } + /* require: .is_empty() = false */ + utc_nanos next_tm() const { return this->event_tm(this->event_heap_.front()); } + /* #of events stored in this reducer */ + uint32_t n_event() const { return this->event_heap_.size(); } - Event const & last_annexed_ev() const { return this->annexed_ev_; } + Event const & last_annexed_ev() const { return this->annexed_ev_; } - void include_event(Event const & ev) { - this->event_heap_.push_back(ev); - std::push_heap(this->event_heap_.begin(), - this->event_heap_.end(), - std::greater()); - } /*include_event*/ + void include_event(Event const & ev) { + this->event_heap_.push_back(ev); + std::push_heap(this->event_heap_.begin(), + this->event_heap_.end(), + std::greater()); + } /*include_event*/ - void include_event(Event && ev) { - this->event_heap_.push_back(std::move(ev)); - std::push_heap(this->event_heap_.begin(), - this->event_heap_.end(), - std::greater()); - } /*include_event*/ + void include_event(Event && ev) { + this->event_heap_.push_back(std::move(ev)); + std::push_heap(this->event_heap_.begin(), + this->event_heap_.end(), + std::greater()); + } /*include_event*/ - Event & annex_one() { - this->annexed_ev_ = this->event_heap_.front(); - std::pop_heap(this->event_heap_.begin(), - this->event_heap_.end(), - std::greater()); - this->event_heap_.pop_back(); + Event & annex_one() { + this->annexed_ev_ = this->event_heap_.front(); + std::pop_heap(this->event_heap_.begin(), + this->event_heap_.end(), + std::greater()); + this->event_heap_.pop_back(); - return this->annexed_ev_; - } /*annex_one*/ + return this->annexed_ev_; + } /*annex_one*/ - // ----- Inherited from ReducerBase ----- + // ----- Inherited from ReducerBase ----- - // utc_nanos event_tm(Event const & x); - - private: - /* queued Events, in increasing timestamp order */ - std::vector event_heap_; - /* annexed event, removed from .event_heap */ - Event annexed_ev_; - }; /*HeapReducer*/ - } /*namespace reactor*/ + // utc_nanos event_tm(Event const & x); + + private: + /* queued Events, in increasing timestamp order */ + std::vector event_heap_; + /* annexed event, removed from .event_heap */ + Event annexed_ev_; + }; /*HeapReducer*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end HeapReducer.hpp */ diff --git a/include/xo/reactor/PollingReactor.hpp b/include/xo/reactor/PollingReactor.hpp index 7dbad4ab..8cbe2c2f 100644 --- a/include/xo/reactor/PollingReactor.hpp +++ b/include/xo/reactor/PollingReactor.hpp @@ -8,37 +8,41 @@ #include namespace xo { - namespace reactor { - /* reactor that runs by polling an ordered set of sources */ - class PollingReactor : public Reactor { - public: - PollingReactor() = default; + namespace reactor { + /* reactor that runs by polling an ordered set of sources */ + class PollingReactor : public Reactor { + public: + /* named ctor idiom */ + static ref::rp make() { return new PollingReactor(); } - // ----- inherited from Reactor ----- + // ----- inherited from Reactor ----- - virtual bool add_source(ref::brw src) override; - virtual bool remove_source(ref::brw src) override; - virtual std::uint64_t run_one() override; + virtual bool add_source(ref::brw src) override; + virtual bool remove_source(ref::brw src) override; + virtual void notify_source_primed(ref::brw src) override; + virtual std::uint64_t run_one() override; - private: - /* find non-empty source, starting from .source_v_[start_ix], - * wrapping around to .source_v_[start_ix - 1]. - * - * return index of first available non-empty source, - * or -1 if all sources are empty - */ - std::int64_t find_nonempty_source(std::size_t start_ix); + private: + PollingReactor() = default; - private: - /* next source to poll will be .source_v_[.next_ix_] */ - std::size_t next_ix_ = 0; + /* find non-empty source, starting from .source_v_[start_ix], + * wrapping around to .source_v_[start_ix - 1]. + * + * return index of first available non-empty source, + * or -1 if all sources are empty + */ + std::int64_t find_nonempty_source(std::size_t start_ix); - /* ordered set of sources (see reactor::Source) - * reactor will poll sources in round-robin order - */ - std::vector source_v_; - }; /*PollingReactor*/ - } /*namespace reactor*/ + private: + /* next source to poll will be .source_v_[.next_ix_] */ + std::size_t next_ix_ = 0; + + /* ordered set of sources (see reactor::Source) + * reactor will poll sources in round-robin order + */ + std::vector source_v_; + }; /*PollingReactor*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end PollingReactor.hpp */ diff --git a/include/xo/reactor/PolyAdapterSink.hpp b/include/xo/reactor/PolyAdapterSink.hpp index e8f68eb9..60ec4ccf 100644 --- a/include/xo/reactor/PolyAdapterSink.hpp +++ b/include/xo/reactor/PolyAdapterSink.hpp @@ -9,84 +9,84 @@ #include "xo/reflect/Reflect.hpp" namespace xo { - namespace reactor { - /* adapter between a source that delivers a particular event type T, - * and a sink that accepts arbitrarily-typed events via .notify_ev_tp() - * Use this to connect to a polymorphic sink. - * - * Require: - * - .poly_sink.allow_polymorphic_source() - * (ofc. otherwise no point in using PolyAdapterSink) - * - .poly_sink.allow_volatile_source() - * need this bc will be wrapping event with TaggedPtr, - * which doesn't manage event lifetime - */ - template - class PolyAdapterSink : public reactor::Sink1 { - public: - using Reflect = reflect::Reflect; - using TaggedPtr = reflect::TaggedPtr; + namespace reactor { + /* adapter between a source that delivers a particular event type T, + * and a sink that accepts arbitrarily-typed events via .notify_ev_tp() + * Use this to connect to a polymorphic sink. + * + * Require: + * - .poly_sink.allow_polymorphic_source() + * (ofc. otherwise no point in using PolyAdapterSink) + * - .poly_sink.allow_volatile_source() + * need this bc will be wrapping event with TaggedPtr, + * which doesn't manage event lifetime + */ + template + class PolyAdapterSink : public reactor::Sink1 { + public: + using Reflect = reflect::Reflect; + using TaggedPtr = reflect::TaggedPtr; - public: - /* named ctor idiom */ - static ref::rp make(ref::rp poly_sink) { - //xo::scope lscope("PolyAdapterSink::make"); + public: + /* named ctor idiom */ + static ref::rp make(ref::rp poly_sink) { + //xo::scope lscope("PolyAdapterSink::make"); - ref::rp retval(new PolyAdapterSink(poly_sink)); + ref::rp retval(new PolyAdapterSink(poly_sink)); - //lscope.log("adapter", (void*)retval.get()); + //lscope.log("adapter", (void*)retval.get()); - return retval; - } /*make*/ + return retval; + } /*make*/ - // ----- Inherited from Sink1 ----- + // ----- Inherited from Sink1 ----- - virtual void notify_ev(T const & ev) override { - //xo::scope lscope("PolyAdapterSink::notify_ev"); - //lscope.log(xo::xtag("ev", ev)); + virtual void notify_ev(T const & ev) override { + //xo::scope lscope("PolyAdapterSink::notify_ev"); + //lscope.log(xo::xtag("ev", ev)); - TaggedPtr ev_tp = Reflect::make_tp(const_cast(&ev)); + TaggedPtr ev_tp = Reflect::make_tp(const_cast(&ev)); - this->notify_ev_tp(ev_tp); - } /*notify_ev*/ + this->notify_ev_tp(ev_tp); + } /*notify_ev*/ - // ----- Inherited from AbstractSink ----- + // ----- Inherited from AbstractSink ----- - virtual bool allow_volatile_source() const override { return true; } - virtual uint32_t n_in_ev() const override { return this->poly_sink_->n_in_ev(); } - /* note: ok to do this, however if expecting to use this entry point, - * maybe don't need to interpose PolyAdapterSink ahead of .poly_sink - */ - virtual void notify_ev_tp(TaggedPtr const & ev_tp) override { - //xo::scope lscope("PolyAdapterSink::notify_ev_tp"); + virtual bool allow_volatile_source() const override { return true; } + virtual uint32_t n_in_ev() const override { return this->poly_sink_->n_in_ev(); } + /* note: ok to do this, however if expecting to use this entry point, + * maybe don't need to interpose PolyAdapterSink ahead of .poly_sink + */ + virtual void notify_ev_tp(TaggedPtr const & ev_tp) override { + //xo::scope lscope("PolyAdapterSink::notify_ev_tp"); - return this->poly_sink_->notify_ev_tp(ev_tp); - } + return this->poly_sink_->notify_ev_tp(ev_tp); + } - // ----- Inherited from AbstractEventProcessor ----- + // ----- Inherited from AbstractEventProcessor ----- - virtual std::string const & name() const override { return this->poly_sink_->name(); } - virtual void set_name(std::string const & x) override { this->poly_sink_->set_name(x); } - virtual void visit_direct_consumers(std::function ep)> const & fn) override { - this->poly_sink_->visit_direct_consumers(fn); - } - virtual void display(std::ostream & os) const override { - using xo::xtag; - os << "()) - << xtag("poly", this->poly_sink_) - << ">"; - } /*display*/ + virtual std::string const & name() const override { return this->poly_sink_->name(); } + virtual void set_name(std::string const & x) override { this->poly_sink_->set_name(x); } + virtual void visit_direct_consumers(std::function ep)> const & fn) override { + this->poly_sink_->visit_direct_consumers(fn); + } + virtual void display(std::ostream & os) const override { + using xo::xtag; + os << "()) + << xtag("poly", this->poly_sink_) + << ">"; + } /*display*/ - private: - PolyAdapterSink(ref::rp poly_sink) : poly_sink_{std::move(poly_sink)} {} + private: + PolyAdapterSink(ref::rp poly_sink) : poly_sink_{std::move(poly_sink)} {} - private: - /* mandate: .poly_sink.allow_polymorphic_source() is true */ - ref::rp poly_sink_; - }; /*PolyAdapterSink*/ - } /*namespace reactor*/ + private: + /* mandate: .poly_sink.allow_polymorphic_source() is true */ + ref::rp poly_sink_; + }; /*PolyAdapterSink*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end PolyAdapterSink.hpp */ diff --git a/include/xo/reactor/Reactor.hpp b/include/xo/reactor/Reactor.hpp index c56a590c..453312a4 100644 --- a/include/xo/reactor/Reactor.hpp +++ b/include/xo/reactor/Reactor.hpp @@ -3,61 +3,69 @@ #pragma once #include "xo/refcnt/Refcounted.hpp" +#include "xo/indentlog/log_level.hpp" #include namespace xo { - namespace reactor { - class ReactorSource; + namespace reactor { + class ReactorSource; - /* abtract api for a reactor: - * something that arranges to have work done on a set of Sources. - */ - class Reactor : public ref::Refcount { - public: - virtual ~Reactor() = default; + /* abtract api for a reactor: + * something that arranges to have work done on a set of Sources. + */ + class Reactor : public ref::Refcount { + public: + virtual ~Reactor() = default; - /* add source src to this reactor. - * on success, invoke src.notify_reactor_add(this) - * - * returns true if source added; false if already present - */ - virtual bool add_source(ref::brw src) = 0; + log_level loglevel() const { return loglevel_; } + void set_loglevel(log_level loglevel) { loglevel_ = loglevel; } - /* remove source src from this reactor. - * source must previously have been added by - * .add_source(src). - * - * on success, invoke src.notify_reactor_remove(this) - * - * returns true if source removed; false if not present - */ - virtual bool remove_source(ref::brw src) = 0; + /* add source src to this reactor. + * on success, invoke src.notify_reactor_add(this) + * + * returns true if source added; false if already present + */ + virtual bool add_source(ref::brw src) = 0; - /* notification when non-primed source (source with no known events) - * becomes primed (source with at least one event) - */ - virtual void notify_source_primed(ref::brw src) = 0; + /* remove source src from this reactor. + * source must previously have been added by + * .add_source(src). + * + * on success, invoke src.notify_reactor_remove(this) + * + * returns true if source removed; false if not present + */ + virtual bool remove_source(ref::brw src) = 0; - /* dispatch one reactor event, borrowing the calling thread - * amount of work this represents is Source/Sink specific. - * - * returns #of events dispatched (0 or 1) - */ - virtual std::uint64_t run_one() = 0; + /* notification when non-primed source (source with no known events) + * becomes primed (source with at least one event) + */ + virtual void notify_source_primed(ref::brw src) = 0; - /* borrow calling thread to dispatch reactor events. - * if n is -1, run indefinitely - * otherwise dispatch up to n events. - * n = 0 is a noop - */ - void run_n(int32_t n); + /* dispatch one reactor event, borrowing the calling thread + * amount of work this represents is Source/Sink specific. + * + * returns #of events dispatched (0 or 1) + */ + virtual std::uint64_t run_one() = 0; - /* borrow calling thread to run indefinitely. - * suitable implementation for dedicated reactor threads - */ - void run() { this->run_n(-1); } - }; /*Reactor*/ - } /*namespace reactor*/ + /* borrow calling thread to dispatch reactor events. + * if n is -1, run indefinitely + * otherwise dispatch up to n events. + * n = 0 is a noop + */ + void run_n(int32_t n); + + /* borrow calling thread to run indefinitely. + * suitable implementation for dedicated reactor threads + */ + void run() { this->run_n(-1); } + + private: + /* control logging verbosity */ + log_level loglevel_; + }; /*Reactor*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end Reactor.hpp */ diff --git a/include/xo/reactor/ReactorSource.hpp b/include/xo/reactor/ReactorSource.hpp index 92f275d4..8b217a4b 100644 --- a/include/xo/reactor/ReactorSource.hpp +++ b/include/xo/reactor/ReactorSource.hpp @@ -7,124 +7,124 @@ #include namespace xo { - namespace reactor { - class Reactor; + namespace reactor { + class Reactor; - /* abstract api for a source of events. - * Event representation is left open: Sources and Sinks - * need to have compatible event representations, - * and coordination is left to such (Source, Sink) pairs. - * - * Source->Sink activity may be expected to be mediated by a reactor, - * that implements the Reactor api. - * - * At any time, A Source can be associated with at most one reactor. - * Sources are informed of Reactor<->Source association being - * formed/broken by the - * .notify_reactor_add(), .notify_reactor_remove() - * methods - * - * The source api intends also to provide for simulation. - * There introduces two simulation-specific methods: - * .sim_current_tm() - * .sim_advance_until() - * - * A non-simulation source can implement these as calls to - * .online_current_tm(), .online_advance_until() respectively - * .online_current_tm() aborts since an online source is never exhausted - * .online_advance_until() is a no-op that returns 0 - * - * Loop for consuming from a primary simulation source: - * - * brw s = ...; - * while(!s->is_exhausted()) - * s->deliver_one(); - * - * Secondary sources (sources that depend on other sources) can be - * in a state where they don't know their next event, in which case: - * - * s->is_notprimed() == true - */ - class ReactorSource : public AbstractSource { - public: - using utc_nanos = xo::time::utc_nanos; + /* abstract api for a source of events. + * Event representation is left open: Sources and Sinks + * need to have compatible event representations, + * and coordination is left to such (Source, Sink) pairs. + * + * Source->Sink activity may be expected to be mediated by a reactor, + * that implements the Reactor api. + * + * At any time, A Source can be associated with at most one reactor. + * Sources are informed of Reactor<->Source association being + * formed/broken by the + * .notify_reactor_add(), .notify_reactor_remove() + * methods + * + * The source api intends also to provide for simulation. + * There introduces two simulation-specific methods: + * .sim_current_tm() + * .sim_advance_until() + * + * A non-simulation source can implement these as calls to + * .online_current_tm(), .online_advance_until() respectively + * .online_current_tm() aborts since an online source is never exhausted + * .online_advance_until() is a no-op that returns 0 + * + * Loop for consuming from a primary simulation source: + * + * brw s = ...; + * while(!s->is_exhausted()) + * s->deliver_one(); + * + * Secondary sources (sources that depend on other sources) can be + * in a state where they don't know their next event, in which case: + * + * s->is_notprimed() == true + */ + class ReactorSource : public AbstractSource { + public: + using utc_nanos = xo::time::utc_nanos; - public: - virtual ~ReactorSource() = default; + public: + virtual ~ReactorSource() = default; - /* true if source is currently empty (has 0 events to deliver) */ - virtual bool is_empty() const = 0; - bool is_nonempty() const { return !this->is_empty(); } + /* true if source is currently empty (has 0 events to deliver) */ + virtual bool is_empty() const = 0; + bool is_nonempty() const { return !this->is_empty(); } - /* true when source knows its next event - * A source that isn't primed is also excluded from simulation - * heap until it becomes primed. - * This make feasible simulation sources that - * depend on other simulation sources - */ - virtual bool is_primed() const { return !this->is_empty(); } - virtual bool is_notprimed() const { return this->is_empty(); } + /* true when source knows its next event + * A source that isn't primed is also excluded from simulation + * heap until it becomes primed. + * This make feasible simulation sources that + * depend on other simulation sources + */ + virtual bool is_primed() const { return !this->is_empty(); } + virtual bool is_notprimed() const { return this->is_empty(); } - /* if true, this source has no events, and will never publish more events - * - for sim, return true for a standalone source that has replayed all events - * - for rt, set during orderly - */ - virtual bool is_exhausted() const = 0; + /* if true, this source has no events, and will never publish more events + * - for sim, return true for a standalone source that has replayed all events + * - for rt, set during orderly + */ + virtual bool is_exhausted() const = 0; - /* if this is a simulation source and .is_exhausted is false: - * returns next event time; more precisely, no events exist prior to - * this time. - * - * if sim, and .is_primed = true, - * returns timestamp of next event - */ - virtual utc_nanos sim_current_tm() const = 0; + /* if this is a simulation source and .is_exhausted is false: + * returns next event time; more precisely, no events exist prior to + * this time. + * + * if sim, and .is_primed = true, + * returns timestamp of next event + */ + virtual utc_nanos sim_current_tm() const = 0; - /* promise: - * - .current_tm() > tm || .is_notprimed() || .is_exhausted() = true - * - if replay_flag is true, then any events between previous .current_tm() - * and new .current_tm() will have been published - * - * returns #of events delivered. - * does not count events that were skipped, so always returns 0 if - * replay_flag is false - */ - virtual std::uint64_t sim_advance_until(utc_nanos tm, bool replay_flag) = 0; + /* promise: + * - .current_tm() > tm || .is_notprimed() || .is_exhausted() = true + * - if replay_flag is true, then any events between previous .current_tm() + * and new .current_tm() will have been published + * + * returns #of events delivered. + * does not count events that were skipped, so always returns 0 if + * replay_flag is false + */ + virtual std::uint64_t sim_advance_until(utc_nanos tm, bool replay_flag) = 0; - /* informs source when it's added to a reactor + /* informs source when it's added to a reactor - * (see Reactor.add_source()) - */ - virtual void notify_reactor_add(Reactor * /*reactor*/) {} + * (see Reactor.add_source()) + */ + virtual void notify_reactor_add(Reactor * /*reactor*/) {} - /* informs source when it's removed from a reactor - * (see Reactor.remove_source()) - */ - virtual void notify_reactor_remove(Reactor * /*reactor*/) {} + /* informs source when it's removed from a reactor + * (see Reactor.remove_source()) + */ + virtual void notify_reactor_remove(Reactor * /*reactor*/) {} - // ----- Inherited from AbstractSource ----- + // ----- Inherited from AbstractSource ----- - /* deliver one event to attached sink - * interpretation of 'one event' is source-specific; - * could be a collapsed or batched event in practice. - * - * no-op if source is empty. - * - * if sim, promise: - * - new .current_tm >= old .current_tm() || .is_notprimed() || .is_exhausted() - * - * returns #of events delivered. Must be 0 or 1 in this context - */ - virtual std::uint64_t deliver_one() override = 0; + /* deliver one event to attached sink + * interpretation of 'one event' is source-specific; + * could be a collapsed or batched event in practice. + * + * no-op if source is empty. + * + * if sim, promise: + * - new .current_tm >= old .current_tm() || .is_notprimed() || .is_exhausted() + * + * returns #of events delivered. Must be 0 or 1 in this context + */ + virtual std::uint64_t deliver_one() override = 0; - protected: - /* default implementations for online sources */ - utc_nanos online_current_tm() const; - uint64_t online_advance_until(utc_nanos tm, bool replay_flag); - }; /*ReactorSource*/ + protected: + /* default implementations for online sources */ + utc_nanos online_current_tm() const; + uint64_t online_advance_until(utc_nanos tm, bool replay_flag); + }; /*ReactorSource*/ - using ReactorSourcePtr = ref::rp; - } /*namespace reactor*/ + using ReactorSourcePtr = ref::rp; + } /*namespace reactor*/ } /*namespace xo*/ /* end ReactorSource.hpp */ diff --git a/include/xo/reactor/SecondarySource.hpp b/include/xo/reactor/SecondarySource.hpp index 398a17bb..c58c2e92 100644 --- a/include/xo/reactor/SecondarySource.hpp +++ b/include/xo/reactor/SecondarySource.hpp @@ -2,358 +2,359 @@ #pragma once -#include "time/Time.hpp" -#include "reactor/Sink.hpp" -#include "reactor/DirectSource.hpp" -#include "reactor/Reactor.hpp" -#include "callback/CallbackSet.hpp" -#include "reflect/demangle.hpp" +//#include "time/Time.hpp" +#include "Sink.hpp" +//#include "xo/reactor/DirectSource.hpp" +#include "Reactor.hpp" +#include "HeapReducer.hpp" +#include "xo/callback/CallbackSet.hpp" +#include "xo/cxxutil/demangle.hpp" #include namespace xo { - namespace reactor { - /* A passive event source. - * Can use as backend publisher when implementating another - * event processor. - */ - template> - class SecondarySource : public EventSource> { - public: - using EventSink = Sink1; - template - using RpCallbackSet = fn::RpCallbackSet; - using CallbackId = fn::CallbackId; - using TypeDescr = xo::reflect::TypeDescr; - using utc_nanos = xo::time::utc_nanos; + namespace reactor { + /* A passive event source. + * Can use as backend publisher when implementating another + * event processor. + */ + template> + class SecondarySource : public EventSource> { + public: + using EventSink = Sink1; + template + using RpCallbackSet = fn::RpCallbackSet; + using CallbackId = fn::CallbackId; + using TypeDescr = xo::reflect::TypeDescr; + using utc_nanos = xo::time::utc_nanos; - public: - ~SecondarySource() = default; + public: + ~SecondarySource() = default; - static ref::rp make() { return new SecondarySource(); } + static ref::rp make() { return new SecondarySource(); } - /* last event delivered from this source -- - * i.e. event in most recent call to .deliver_one_aux() - */ - Event const & last_annexed_ev() const { return this->reducer_.last_annexed_ev(); } + /* last event delivered from this source -- + * i.e. event in most recent call to .deliver_one_aux() + */ + Event const & last_annexed_ev() const { return this->reducer_.last_annexed_ev(); } - void notify_upstream_exhausted() { this->upstream_exhausted_ = true; } + void notify_upstream_exhausted() { this->upstream_exhausted_ = true; } - /* make event available to reactor, by adding to internal reducer */ - void notify_secondary_event(Event const & ev) { - /* test if ev is priming, update .current_tm */ - bool is_priming = this->preprocess_secondary_event(ev); + /* make event available to reactor, by adding to internal reducer */ + void notify_secondary_event(Event const & ev) { + /* test if ev is priming, update .current_tm */ + bool is_priming = this->preprocess_secondary_event(ev); - this->reducer_.include_event(ev); + this->reducer_.include_event(ev); - this->postprocess_secondary_event(is_priming); - } /*notify_secondary_event*/ + this->postprocess_secondary_event(is_priming); + } /*notify_secondary_event*/ - void notify_secondary_event(Event && ev) { - bool is_priming = this->preprocess_secondary_event(ev); + void notify_secondary_event(Event && ev) { + bool is_priming = this->preprocess_secondary_event(ev); - this->reducer_.include_event(ev); + this->reducer_.include_event(ev); - this->postprocess_secondary_event(is_priming); - } /*notify_secondary_event*/ + this->postprocess_secondary_event(is_priming); + } /*notify_secondary_event*/ - template - void notify_secondary_event_v(T const & v) { - using xo::scope; - using xo::xtag; + template + void notify_secondary_event_v(T const & v) { + using xo::scope; + using xo::xtag; - if (v.empty()) - return; + if (v.empty()) + return; - scope log(XO_DEBUG(this->debug_sim_flag_)); + scope log(XO_DEBUG(this->debug_sim_flag_)); - log && log(xtag("name", this->name())); + log && log(xtag("name", this->name())); - if (this->upstream_exhausted_) { - throw std::runtime_error("SecondarySource::notify_secondary_event_v" - ": not allowed after upstream exhausted"); + if (this->upstream_exhausted_) { + throw std::runtime_error("SecondarySource::notify_secondary_event_v" + ": not allowed after upstream exhausted"); + } + + uint32_t n_ev = 0; + + for (Event const & ev : v) { + utc_nanos evtm = this->reducer_.event_tm(ev); + + if (this->current_tm_ < evtm) + this->current_tm_ = evtm; + + ++n_ev; + } + + log && log(xtag("T", reflect::type_name()), + xtag("n_ev", n_ev)); + + if (n_ev > 0) { + /* if reducer is empty when .notify_secondary_event_v() begins, + * then reactor/simulator needs to be notified that source is no longer empty + */ + bool is_priming = this->reducer_.is_empty(); + + for (Event const & ev : v) + this->reducer_.include_event(ev); + + Reactor * reactor = this->parent_reactor_; + + if (reactor) { + if (is_priming) { + /* reactor/simulator takes responsibility for delivering events */ + reactor->notify_source_primed(ref::brw::from_native(this)); + } + } else { + /* special case if no reactor: deliver immediately */ + + //this->deliver_one(); + this->deliver_all(); + } + } + } /*notify_secondary_event_v*/ + + // ----- inherited from EventSource ----- + + virtual CallbackId add_callback(ref::rp const & cb) override { + return this->cb_set_.add_callback(cb); + } /*add_callback*/ + + virtual void remove_callback(CallbackId id) override { + this->cb_set_.remove_callback(id); + } /*remove_callback*/ + + // ----- inherited from ReactorSource ----- + + virtual bool is_empty() const override { return this->reducer_.is_empty(); } + virtual bool is_exhausted() const override { return this->upstream_exhausted_ && this->is_empty(); } + + virtual utc_nanos sim_current_tm() const override { + using xo::scope; + using xo::xtag; + + if (this->reducer_.is_empty()) { + /* this is a tricky case. + * it means this source doesn't + * _know_ specific next event yet; however new events + * may appear at any time by way of .notify_event() + * + * If event doesn't know next event, then .current_tm isn't useful + * for establishing priority relative to other sources. + * rely on priming mechanism instead, + * which means that control should never come here. + */ + return this->current_tm_; + } else { + scope log(XO_DEBUG(false /*this->debug_sim_flag_*/), + xtag("name", this->name_), + xtag("next_tm", this->reducer_.next_tm())); + + return this->reducer_.next_tm(); + } + } /*sim_current_tm*/ + + virtual std::uint64_t deliver_one() override { + return this->deliver_one_aux(true /*replay_flag*/); } - uint32_t n_ev = 0; + virtual std::uint64_t sim_advance_until(utc_nanos target_tm, + bool replay_flag) override + { + uint64_t retval = 0; - for (Event const & ev : v) { - utc_nanos evtm = this->reducer_.event_tm(ev); + while (!this->reducer_.is_empty()) { + utc_nanos tm = this->sim_current_tm(); - if (this->current_tm_ < evtm) - this->current_tm_ = evtm; + if (tm < target_tm) { + retval += this->deliver_one_aux(replay_flag); + } else { + break; + } + } - ++n_ev; - } + return retval; + } /*sim_advance_until*/ - log && log(xtag("T", reflect::type_name()), - xtag("n_ev", n_ev)); + virtual void notify_reactor_add(Reactor * reactor) override { + assert(!this->parent_reactor_); - if (n_ev > 0) { - /* if reducer is empty when .notify_secondary_event_v() begins, - * then reactor/simulator needs to be notified that source is no longer empty - */ - bool is_priming = this->reducer_.is_empty(); + this->parent_reactor_ = reactor; + } /*notify_reactor_add*/ - for (Event const & ev : v) - this->reducer_.include_event(ev); + virtual void notify_reactor_remove(Reactor * /*reactor*/) override {} + + // ----- inherited from AbstractSource ----- + + virtual TypeDescr source_ev_type() const override { + return reflect::Reflect::require(); + } /*source_ev_type*/ + + virtual uint32_t n_out_ev() const override { return n_out_ev_; } + /* #of events queued for delivery */ + virtual uint32_t n_queued_out_ev() const override { return this->reducer_.n_event(); } + + virtual bool debug_sim_flag() const override { return debug_sim_flag_; } + virtual void set_debug_sim_flag(bool x) override { this->debug_sim_flag_ = x; } + + virtual CallbackId attach_sink(ref::rp const & sink) override { + ref::rp native_sink + = EventSink::require_native("SecondarySource::attach_sink", sink); + + if (native_sink) { + if (!this->is_volatile() + || native_sink->allow_volatile_source()) + { + return this->add_callback(native_sink); + } else { + throw std::runtime_error("SecondarySource::attach_sink" + ": sink requires non-volatile source " + + std::string(reflect::type_name())); + } + } else { + throw std::runtime_error("SecondarySource::attach_sink" + ": expected sink accepting " + + std::string(reflect::type_name())); + } + } /*attach_sink*/ + + virtual void detach_sink(CallbackId id) override { + this->remove_callback(id); + } /*detach_sink*/ + + // ----- Inherited from AbstractEventProcessor ----- + + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { this->name_ = x; } + + virtual void visit_direct_consumers(std::function ep)> const & fn) override { + + for(auto x : this->cb_set_) + fn(x.fn_.borrow()); + } /*visit_direct_consumers*/ + + private: + /* event book-keeping on receiving an event. + */ + bool preprocess_secondary_event(Event const & ev) + { + if (this->upstream_exhausted_) { + throw std::runtime_error("SecondarySource::notify_secondary_event" + ": not allowed after upstream exhausted"); + } + + utc_nanos evtm = this->reducer_.event_tm(ev); + + if (this->current_tm_ < evtm) + this->current_tm_ = evtm; + + /* if reducer is empty when .notify_event() begins, + * then reactor/simulator needs to be notified that source is no longer empty + */ + bool is_priming = this->reducer_.is_empty(); + + return is_priming; + } /*preprocess_secondary_event*/ + + /* event bookkeeping after receiving an event. + * + * Require: event has been propagated to .reducer + * + * is_priming. true if event causes source to + * become non-empty --> must notify reactor + */ + void postprocess_secondary_event(bool is_priming) { + using xo::scope; + using xo::xtag; Reactor * reactor = this->parent_reactor_; + scope log(XO_DEBUG(this->debug_sim_flag_), + xtag("name", name_), + xtag("reactor", (void*)reactor), + xtag("is_priming", is_priming)); + if (reactor) { if (is_priming) { /* reactor/simulator takes responsibility for delivering events */ reactor->notify_source_primed(ref::brw::from_native(this)); } } else { - /* special case if no reactor: deliver immediately */ - - //this->deliver_one(); - this->deliver_all(); + /* if no reactor, deliver immediately */ + this->deliver_one(); } - } - } /*notify_secondary_event_v*/ + } /*postprocess_secondary_event*/ - // ----- inherited from EventSource ----- - - CallbackId add_callback(ref::rp const & cb) override { - return this->cb_set_.add_callback(cb); - } /*add_callback*/ - - void remove_callback(CallbackId id) override { - this->cb_set_.remove_callback(id); - } /*remove_callback*/ - - // ----- inherited from ReactorSource ----- - - virtual bool is_empty() const override { return this->reducer_.is_empty(); } - virtual bool is_exhausted() const override { return this->upstream_exhausted_ && this->is_empty(); } - - virtual utc_nanos sim_current_tm() const override { - using xo::scope; - using xo::xtag; - - if (this->reducer_.is_empty()) { - /* this is a tricky case. - * it means this source doesn't - * _know_ specific next event yet; however new events - * may appear at any time by way of .notify_event() - * - * If event doesn't know next event, then .current_tm isn't useful - * for establishing priority relative to other sources. - * rely on priming mechanism instead, - * which means that control should never come here. - */ - return this->current_tm_; - } else { - scope log(XO_DEBUG(false /*this->debug_sim_flag_*/), + /* deliver one event from reducer; + * invoke callback whenever replay_flag is true + */ + std::uint64_t deliver_one_aux(bool replay_flag) { + scope log(XO_DEBUG(this->debug_sim_flag_), xtag("name", this->name_), - xtag("next_tm", this->reducer_.next_tm())); + xtag("reducer.empty", this->reducer_.is_empty()), + xtag("replay_flag", replay_flag)); - return this->reducer_.next_tm(); - } - } /*sim_current_tm*/ + if (this->reducer_.is_empty()) + return 0; - virtual std::uint64_t deliver_one() override { - return this->deliver_one_aux(true /*replay_flag*/); - } + /* need to remove event _before_ invoking callbacks; + * callbacks may indirectly call this->notify_secondary_event(), + * modifiying .reducer + * + * reducer may use double-buffering scheme or similar to + * mitigate copying, esp when Event objects are heavy + */ + Event & ev = this->reducer_.annex_one(); - virtual std::uint64_t sim_advance_until(utc_nanos target_tm, - bool replay_flag) override - { - uint64_t retval = 0; + /* if SecondarySource: + * Event ev = this->event_heap_.front(); + * std::pop_heap(this->event_heap_.begin(), + * this->event_heap_.end(), + * std::greater()); + * this->event_heap_.pop_back(); + */ - while (!this->reducer_.is_empty()) { - utc_nanos tm = this->sim_current_tm(); - - if (tm < target_tm) { - retval += this->deliver_one_aux(replay_flag); - } else { - break; - } - } - - return retval; - } /*sim_advance_until*/ - - virtual void notify_reactor_add(Reactor * reactor) override { - assert(!this->parent_reactor_); - - this->parent_reactor_ = reactor; - } /*notify_reactor_add*/ - - virtual void notify_reactor_remove(Reactor * /*reactor*/) override {} - - // ----- inherited from AbstractSource ----- - - virtual TypeDescr source_ev_type() const override { - return reflect::Reflect::require(); - } /*source_ev_type*/ - - virtual uint32_t n_out_ev() const override { return n_out_ev_; } - /* #of events queued for delivery */ - virtual uint32_t n_queued_out_ev() const override { return this->reducer_.n_event(); } - - virtual bool debug_sim_flag() const override { return debug_sim_flag_; } - virtual void set_debug_sim_flag(bool x) override { this->debug_sim_flag_ = x; } - - virtual CallbackId attach_sink(ref::rp const & sink) override { - ref::rp native_sink - = EventSink::require_native("SecondarySource::attach_sink", sink); - - if (native_sink) { - if (!this->is_volatile() - || native_sink->allow_volatile_source()) - { - return this->add_callback(native_sink); - } else { - throw std::runtime_error("SecondarySource::attach_sink" - ": sink requires non-volatile source " - + std::string(reflect::type_name())); - } - } else { - throw std::runtime_error("SecondarySource::attach_sink" - ": expected sink accepting " - + std::string(reflect::type_name())); - } - } /*attach_sink*/ - - virtual void detach_sink(CallbackId id) override { - this->remove_callback(id); - } /*detach_sink*/ - - // ----- Inherited from AbstractEventProcessor ----- - - virtual std::string const & name() const override { return name_; } - virtual void set_name(std::string const & x) override { this->name_ = x; } - - virtual void visit_direct_consumers(std::function ep)> const & fn) override { - - for(auto x : this->cb_set_) - fn(x.fn_.borrow()); - } /*visit_direct_consumers*/ - - private: - /* event book-keeping on receiving an event. - */ - bool preprocess_secondary_event(Event const & ev) - { - if (this->upstream_exhausted_) { - throw std::runtime_error("SecondarySource::notify_secondary_event" - ": not allowed after upstream exhausted"); - } - - utc_nanos evtm = this->reducer_.event_tm(ev); - - if (this->current_tm_ < evtm) - this->current_tm_ = evtm; - - /* if reducer is empty when .notify_event() begins, - * then reactor/simulator needs to be notified that source is no longer empty - */ - bool is_priming = this->reducer_.is_empty(); - - return is_priming; - } /*preprocess_secondary_event*/ - - /* event bookkeeping after receiving an event. - * - * Require: event has been propagated to .reducer - * - * is_priming. true if event causes source to - * become non-empty --> must notify reactor - */ - void postprocess_secondary_event(bool is_priming) { - using xo::scope; - using xo::xtag; - - Reactor * reactor = this->parent_reactor_; - - scope log(XO_DEBUG(this->debug_sim_flag_), - xtag("name", name_), - xtag("reactor", (void*)reactor), - xtag("is_priming", is_priming)); - - if (reactor) { - if (is_priming) { - /* reactor/simulator takes responsibility for delivering events */ - reactor->notify_source_primed(ref::brw::from_native(this)); + if (replay_flag) { + ++(this->n_out_ev_); + this->cb_set_.invoke(&EventSink::notify_ev, ev); } - } else { - /* if no reactor, deliver immediately */ - this->deliver_one(); - } - } /*postprocess_secondary_event*/ - /* deliver one event from reducer; - * invoke callback whenever replay_flag is true - */ - std::uint64_t deliver_one_aux(bool replay_flag) { - scope log(XO_DEBUG(this->debug_sim_flag_), - xtag("name", this->name_), - xtag("reducer.empty", this->reducer_.is_empty()), - xtag("replay_flag", replay_flag)); + return 1; + } /*deliver_one_aux*/ - if (this->reducer_.is_empty()) - return 0; + private: + /* current time for this source */ + utc_nanos current_tm_; - /* need to remove event _before_ invoking callbacks; - * callbacks may indirectly call this->notify_secondary_event(), - * modifiying .reducer - * - * reducer may use double-buffering scheme or similar to - * mitigate copying, esp when Event objects are heavy + /* reporting name for this source (use when .debug_sim_flag set) */ - Event & ev = this->reducer_.annex_one(); + std::string name_; - /* if SecondarySource: - * Event ev = this->event_heap_.front(); - * std::pop_heap(this->event_heap_.begin(), - * this->event_heap_.end(), - * std::greater()); - * this->event_heap_.pop_back(); + /* if true, reactor/simulator to log interaction with this source */ + bool debug_sim_flag_ = false; - if (replay_flag) { - ++(this->n_out_ev_); - this->cb_set_.invoke(&EventSink::notify_ev, ev); - } + /* count lifetime #of outgoing events */ + uint32_t n_out_ev_ = 0; - return 1; - } /*deliver_one_aux*/ + /* set this to true, once, to announce that upstream will send + * no more events. see .notify_upstream_exhausted() + */ + bool upstream_exhausted_ = false; - private: - /* current time for this source */ - utc_nanos current_tm_; + /* events to be delivered to callbacks. + * multiple events may be collapsed depending on Reducer implementation + */ + Reducer reducer_; - /* reporting name for this source (use when .debug_sim_flag set) - */ - std::string name_; + /* reactor/simulator being used to schedule consumption. if ommitted, + * will borrow thread calling .notify_secondary_event() + */ + Reactor * parent_reactor_ = nullptr; - /* if true, reactor/simulator to log interaction with this source - */ - bool debug_sim_flag_ = false; - - /* count lifetime #of outgoing events */ - uint32_t n_out_ev_ = 0; - - /* set this to true, once, to announce that upstream will send - * no more events. see .notify_upstream_exhausted() - */ - bool upstream_exhausted_ = false; - - /* events to be delivered to callbacks. - * multiple events may be collapsed depending on Reducer implementation - */ - Reducer reducer_; - - /* reactor/simulator being used to schedule consumption. if ommitted, - * will borrow thread calling .notify_secondary_event() - */ - Reactor * parent_reactor_ = nullptr; - - /* invoke callbacks in this set to send an outgoing event */ - RpCallbackSet cb_set_; - }; /*SecondarySource*/ - } /*namespace reactor*/ + /* invoke callbacks in this set to send an outgoing event */ + RpCallbackSet cb_set_; + }; /*SecondarySource*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end SecondarySource.hpp */ diff --git a/src/reactor/PollingReactor.cpp b/src/reactor/PollingReactor.cpp index 348ca739..a9c52cb7 100644 --- a/src/reactor/PollingReactor.cpp +++ b/src/reactor/PollingReactor.cpp @@ -45,6 +45,11 @@ namespace xo { return false; } /*remove_source*/ + void + PollingReactor::notify_source_primed(brw) { + /* nothing to do here -- all sources always checked by polling loop */ + } /*notify_source_primed*/ + int64_t PollingReactor::find_nonempty_source(size_t start_ix) { @@ -74,13 +79,25 @@ namespace xo { { int64_t ix = this->find_nonempty_source(this->next_ix_); + scope log(XO_DEBUG(this->loglevel() == log_level::chatty)); + + log && log(xtag("self", this), xtag("src_ix", ix)); + + uint64_t retval = 0; + if(ix >= 0) { brw src = this->source_v_[ix]; - return src->deliver_one(); + log && log(xtag("src.name", src->name())); + + retval = src->deliver_one(); } else { - return 0; + retval = 0; } + + log.end_scope(xtag("retval", retval)); + + return retval; } /*run_one*/ } /*namespace reactor*/ } /*namespace xo*/ diff --git a/src/reactor/ReactorSource.cpp b/src/reactor/ReactorSource.cpp index 0bb29397..5f3735ea 100644 --- a/src/reactor/ReactorSource.cpp +++ b/src/reactor/ReactorSource.cpp @@ -1,4 +1,4 @@ -/* @file Source.cpp */ +/* @file ReactorSource.cpp */ #include "ReactorSource.hpp" #include "xo/indentlog/print/time.hpp" From 1192ef307321d3e4b49af15b9ad45860047e04de Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 17:46:36 -0400 Subject: [PATCH 0210/2524] + EventTimeFn<> template; automate common timestamp patterns --- include/xo/reactor/EventTimeFn2.hpp | 60 +++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 include/xo/reactor/EventTimeFn2.hpp diff --git a/include/xo/reactor/EventTimeFn2.hpp b/include/xo/reactor/EventTimeFn2.hpp new file mode 100644 index 00000000..6d2b0170 --- /dev/null +++ b/include/xo/reactor/EventTimeFn2.hpp @@ -0,0 +1,60 @@ +/* @file EventTimeFn2.hpp */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include + +namespace xo { + namespace reactor { + template + class EventTimeFn { + public: + using utc_nanos = xo::time::utc_nanos; + using event_t = Event; + + public: + static utc_nanos event_tm(event_t const & ev) { return ev.tm(); } + + utc_nanos operator()(event_t const & ev) const { return EventTimeFn::event_tm(ev); } + }; + + template + class EventTimeFn> { + public: + using utc_nanos = xo::time::utc_nanos; + using event_t = xo::ref::rp; + + public: + static utc_nanos event_tm(event_t const & ev) { return ev->tm(); } + + utc_nanos operator()(event_t const & ev) const { return EventTimeFn::event_tm(ev); } + }; + + template + class EventTimeFn { + public: + using utc_nanos = xo::time::utc_nanos; + using event_t = T*; + + public: + static utc_nanos event_tm(event_t ev) { return ev->tm(); } + + utc_nanos operator()(event_t const & ev) const { return EventTimeFn::event_tm(ev); } + }; + + template + class EventTimeFn> { + public: + using utc_nanos = xo::time::utc_nanos; + using event_t = std::pair; + + public: + static utc_nanos event_tm(event_t const & ev) { return ev.first; } + + utc_nanos operator()(event_t const & ev) const { return EventTimeFn::event_tm(ev); } + }; + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end EventTimeFn2.hpp */ From 1d7de75889a90a90fd30486c65877a7612576609 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 17:47:07 -0400 Subject: [PATCH 0211/2524] + FifoQueue --- include/xo/reactor/FifoQueue.hpp | 267 +++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 include/xo/reactor/FifoQueue.hpp diff --git a/include/xo/reactor/FifoQueue.hpp b/include/xo/reactor/FifoQueue.hpp new file mode 100644 index 00000000..175836b3 --- /dev/null +++ b/include/xo/reactor/FifoQueue.hpp @@ -0,0 +1,267 @@ +/* @file FifoQueue.hpp */ + +#pragma once + +#include "Reactor.hpp" +#include "EventSource.hpp" +#include "Sink.hpp" +#include "EventTimeFn2.hpp" +#include "xo/callback/CallbackSet.hpp" +#include + +namespace xo { + namespace reactor { + /* require: + * T null constructible + * T movable + * + * T satisfies EventTimeConcept + */ + template > + class FifoQueue : public virtual Sink1, public virtual EventSource> { + public: + using EventSink = Sink1; + template + using RpCallbackSet = xo::fn::RpCallbackSet; + using CallbackId = xo::fn::CallbackId; + using Reflect = xo::reflect::Reflect; + using TypeDescr = xo::reflect::TypeDescr; + using utc_nanos = xo::time::utc_nanos; + + public: + static ref::rp make(EvTimeFn evtm_fn = EvTimeFn()) { return new FifoQueue(evtm_fn); } + + // ----- inherited from Sink1 ----- + + virtual void notify_ev(T const & ev) override { + bool is_priming = this->elt_q_.empty(); + + this->elt_q_.push_back(ev); + + ++(this->n_in_ev_); + + if (this->upstream_exhausted_) { + throw std::runtime_error("FifoQueue::notify_ev" + ": not allowed after upstream exhausted"); + } + + utc_nanos tm = evtm_fn_(ev); + + if (this->current_tm_ < tm) + this->current_tm_ = tm; + + Reactor * reactor = this->parent_reactor_; + + scope log(XO_DEBUG(this->debug_sim_flag_), + xtag("name", name_), + xtag("reactor", (void*)reactor), + xtag("is_priming", is_priming)); + + if (reactor) { + if (is_priming) { + /* reactor/simulator takes delivery/sequencing responsibility from here */ + reactor->notify_source_primed(ref::brw::from_native(this)); + } + } else { + /* if no reactor, deliver immediately */ + this->deliver_one(); + } + } /*notify_ev*/ + + // ----- inherited from AbstractSink ----- + + /* we don't care about volatile sources -- fifo queue copies incoming events */ + virtual bool allow_volatile_source() const override { return true; } + + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + + // ----- inherited from ReactorSource ----- + + virtual bool is_empty() const override { return elt_q_.empty(); } + virtual bool is_exhausted() const override { return this->upstream_exhausted_ && this->is_empty(); } + + virtual utc_nanos sim_current_tm() const override { + if (this->elt_q_.empty()) { + /* (in practice control never comes here) + * + * queue doesn't know time of next event yet; + * new events may appear at any time by way of .notify_event() + * + * if queue doesn't know next event, can't use .sim_current_tm + * to establish priority relative to other sources. + * In that case rely instead on priming mechanism; + * priming mechanism implies control should never come here + */ + return this->current_tm_; + } else { + return evtm_fn_(this->elt_q_.front()); + } + } /*sim_current_tm*/ + + virtual uint64_t deliver_one() override { + return this->deliver_one_aux(true /*replay_flag*/); + } /*deliver_one*/ + + virtual uint64_t sim_advance_until(utc_nanos target_tm, + bool replay_flag) override { + uint64_t retval = 0; + + while (!this->elt_q_.empty()) { + utc_nanos tm = evtm_fn_(this->elt_q_.front()); + + if (tm < target_tm) { + retval += this->deliver_one_aux(replay_flag); + } else { + break; + } + } + + return retval; + } /*sim_advance_until*/ + + virtual void notify_reactor_add(Reactor * reactor) override { + assert(!this->parent_reactor_); + + this->parent_reactor_ = reactor; + } /*notify_reactor_add*/ + + virtual void notify_reactor_remove(Reactor *) override { + this->parent_reactor_ = nullptr; + } + + // ----- inherited from AbstractSource ----- + + virtual TypeDescr source_ev_type() const override { return Reflect::require(); } + /* events must be copied objects owned by FifoQueue. + * not expected to be pointers to shared storage or something + */ + virtual bool is_volatile() const override { return false; } + virtual uint32_t n_queued_out_ev() const override { return elt_q_.size(); } + virtual uint32_t n_out_ev() const override { return n_out_ev_; } + virtual bool debug_sim_flag() const override { return debug_sim_flag_; } + virtual void set_debug_sim_flag(bool x) override { this->debug_sim_flag_ = x; } + + virtual CallbackId attach_sink(ref::rp const & sink) override { + ref::rp native_sink + = EventSink::require_native("FifoQueue::attach_sink", sink); + + if (native_sink) { + if (!this->is_volatile() + || native_sink->allow_volatile_source()) + { + return this->add_callback(native_sink); + } else { + throw std::runtime_error("FifoQueue::attach_sink" + ": sink requires non-volatile source " + + std::string(reflect::type_name())); + } + } else { + throw std::runtime_error("FifoQueue::attach_sink" + ": expected sink accepting " + + std::string(reflect::type_name())); + } + } /*attach_sink*/ + + virtual void detach_sink(CallbackId id) override { + this->remove_callback(id); + } + + // ----- inherited from EventSource ----- + + virtual CallbackId add_callback(ref::rp const & cb) override { + return this->cb_set_.add_callback(cb); + } + + virtual void remove_callback(CallbackId id) override { + this->cb_set_.remove_callback(id); + } + + // ----- inherited from AbstractEventProcessor ----- + + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { this->name_ = x; } + + virtual void visit_direct_consumers(std::function ep)> const & fn) override { + for (auto x : this->cb_set_) + fn(x.fn_.borrow()); + } /*visit_direct_consumers*/ + + /* write human-readable representation to stream */ + virtual void display(std::ostream & os) const override { + os << "()) + << ">"; + } /*display*/ + + private: + FifoQueue(EvTimeFn evtm_fn) : evtm_fn_{std::move(evtm_fn)} {} + + uint64_t deliver_one_aux(bool replay_flag) { + scope log(XO_DEBUG(this->debug_sim_flag_), + xtag("name", this->name_), + xtag("elt_q.size", this->elt_q_.size()), + xtag("replay_flag", replay_flag)); + + if (this->elt_q_.empty()) + return 0; + + /* avoiding copy for efficiently-swappable T */ + T ev; + std::swap(ev, this->elt_q_.front()); + + this->elt_q_.pop_front(); + + if (replay_flag) { + log && log(xtag("deliver-ev", ev), + xtag("elt_q.size", this->elt_q_.size())); + + ++(this->n_out_ev_); + this->cb_set_.invoke(&EventSink::notify_ev, ev); + } + + return 1; + } /*deliver_one_aux*/ + + private: + /* name (ideally unique) for this queue */ + std::string name_; + + /* extract timestamp from an event */ + EvTimeFn evtm_fn_; + + /* if true, simulator/reactor will report interaction with this source */ + bool debug_sim_flag_ = false; + + /* largest event timestamp delivered + * (monotonically increases, event if events received out-of-timestamp-order) + */ + utc_nanos current_tm_; + + /* events waiting for delivery */ + std::deque elt_q_; + + /* lifetime #of events received */ + uint32_t n_in_ev_ = 0; + /* lifetime #of events delivered */ + uint32_t n_out_ev_ = 0; + + /* set to true, once, to announce that upstream will send no more events. + * see .notify_upstream_exhausted() ? + */ + bool upstream_exhausted_ = false; + + /* reactor/simulator being used to schedule event consumption. + * if omitted, borrow calling thread + */ + Reactor * parent_reactor_ = nullptr; + + /* invoke callbacks in this set to deliver queued events */ + RpCallbackSet cb_set_; + + }; /*FifoQueue*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end FifoQueue.hpp */ From 6fd18236eb3733385d22497cac50acbb32bd3c13 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 17:47:19 -0400 Subject: [PATCH 0212/2524] + unit test --- CMakeLists.txt | 1 + utest/CMakeLists.txt | 35 +++++ utest/PollingReactor.test.cpp | 233 ++++++++++++++++++++++++++++++++++ utest/Sink.test.cpp | 100 +++++++++++++++ utest/reactor_utest_main.cpp | 6 + 5 files changed, 375 insertions(+) create mode 100644 utest/CMakeLists.txt create mode 100644 utest/PollingReactor.test.cpp create mode 100644 utest/Sink.test.cpp create mode 100644 utest/reactor_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bbe8050..5ddd7483 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ xo_toplevel_compile_options() # ---------------------------------------------------------------- add_subdirectory(src/reactor) +add_subdirectory(utest) # ---------------------------------------------------------------- # provide find_pacakge() support for reactor customers diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..1ab4b440 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,35 @@ +# build unittest reactor/unittest' + +set(SELF_EXE utest.reactor) +set(SELF_SRCS Sink.test.cpp PollingReactor.test.cpp reactor_utest_main.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +target_code_coverage(${SELF_EXE} AUTO ALL) + +# ---------------------------------------------------------------- +# internal dependency (on this codebase) + +xo_self_dependency(${SELF_EXE} reactor) + +# ---------------------------------------------------------------- +# external dependencies + +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# should be getting this via xo_include_options2() + +## ---------------------------------------------------------------- +## make standard directories for std:: includes explicit +## so that +## (1) they appear in compile_commands.json. +## (2) clangd (run from emacs lsp-mode) can find them +## +#if(CMAKE_EXPORT_COMPILE_COMMANDS) +# set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES +# ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +#endif() + +# end CMakeLists.txt diff --git a/utest/PollingReactor.test.cpp b/utest/PollingReactor.test.cpp new file mode 100644 index 00000000..39845264 --- /dev/null +++ b/utest/PollingReactor.test.cpp @@ -0,0 +1,233 @@ +/* @file PollingReactor.test.cpp */ + +#include "xo/reactor/PollingReactor.hpp" +#include "xo/reactor/FifoQueue.hpp" +#include "xo/reactor/Sink.hpp" +#include "xo/randomgen/xoshiro256.hpp" +#include "xo/indentlog/print/pair.hpp" +#include "catch2/catch.hpp" + +namespace xo { + //using xo::reactor::Reactor; + using xo::reactor::PollingReactor; + using xo::reactor::FifoQueue; + using xo::reactor::SinkToFunction; + using xo::ref::rp; + using xo::time::timeutil; + using xo::time::seconds; + using xo::time::utc_nanos; + +/* note: trivial REQUIRE() call in else branch bc we still want + * catch2 to count assertions when verification succeeds + */ +# define REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr) \ + if (catch_flag) { \ + REQUIRE((expr)); \ + } else { \ + REQUIRE(true); \ + ok_flag &= (expr); \ + } + +# define REQUIRE_ORFAIL(ok_flag, catch_flag, expr) \ + REQUIRE_ORCAPTURE(ok_flag, catch_flag, expr); \ + if (!ok_flag) \ + return ok_flag + + namespace { + using TestEvent = std::pair; + using TestQueue = FifoQueue; + + struct RandomTestData { + RandomTestData(std::size_t n, + xo::rng::xoshiro256ss * p_rgen); + + std::uint32_t size() const { return u1v_.size(); } + std::vector const & u1v() const { return u1v_; } + + private: + /* a set of n randomly chosen elements drawn from [0 .. 2n-1] */ + std::vector u1v_; + }; + + RandomTestData::RandomTestData(std::size_t n, + xo::rng::xoshiro256ss * p_rgen) + : u1v_(n) + { + std::shuffle(u1v_.begin(), u1v_.end(), *p_rgen); + } + } /*namespace*/ + + namespace ut { + TEST_CASE("polling0", "[reactor]") { + rp reactor = PollingReactor::make(); + + REQUIRE(reactor.get()); + + for (std::uint32_t i=0; i<3; ++i) { + INFO(xtag("i", i)); + REQUIRE(reactor->run_one() == 0); + } + } /*TEST_CASE(polling0)*/ + + /* return true=success, false=fail */ + bool + run_polling1_test(std::size_t n, + bool catch_flag, + xo::rng::xoshiro256ss * p_rgen) + { + scope log(XO_DEBUG(catch_flag)); + log && log(xtag("n", n)); + + bool ok_flag = true; + + rp reactor = PollingReactor::make(); + REQUIRE_ORFAIL(ok_flag, catch_flag, reactor.get() != nullptr); + + if (ok_flag) + reactor->set_loglevel(catch_flag + ? log_level::always + : log_level::error); + + rp q = TestQueue::make(); + REQUIRE_ORFAIL(ok_flag, catch_flag, q.get() != nullptr); + + if (ok_flag) + q->set_name("fifo"); + + /* capture delivered events */ + std::vector out_ev_v; + + auto sink_fn + = ([&out_ev_v](TestEvent const & x) { out_ev_v.push_back(x); }); + + q->add_callback(new SinkToFunction + >(sink_fn)); + + + reactor->add_source(q); + + /* max #of consecutive inserts */ + std::size_t max_enq = std::max(1UL, n/3); + /* max #of consecutive removes */ + std::size_t max_deq = std::max(1UL, n/3); + + RandomTestData seq(n, p_rgen); + + q->set_debug_sim_flag(catch_flag); + + /* verify: + * 1. queue conservation -- everything inserted gets delivered + * 2. events consumed in the same order they where inserted + * 3. no problem with queue being sometimes empty + */ + + utc_nanos t0 = timeutil::ymd_hms(20231011 /*ymd*/, 131300 /*hms*/); + + /* count #of events delivered by reactor */ + std::size_t n_delivered = 0; + + std::size_t i = 0; + while ((i < seq.u1v().size()) || (n_delivered < n)) { + /* sum of (#of enq, #of deq) attempted for this iteration */ + std::size_t n_work_attempted = 0; + /* sum of (#of enq, #of deq) accomplished for this iteration */ + std::size_t n_work_done = 0; + std::size_t n_enq = p_rgen->generate() % (max_enq + 1); + std::size_t n_deq_attempted = 1 + (p_rgen->generate() % (max_deq + 1)); + std::size_t n_deq_done = 0; + + /* pick random #of elements to insert (to back of queue) */ + { + for (std::size_t j = 0; (j < n_enq) && (i < seq.u1v().size()); ++j) { + utc_nanos ti = t0 + seconds(i); + + q->notify_ev(std::make_pair(ti, seq.u1v()[i++])); + } + + n_work_attempted += n_enq; + n_work_done += n_enq; + } + + /* pick random #of elements to remove (from front of queue) */ + { + + + for (std::size_t j = 0; j < n_deq_attempted; ++j) + n_deq_done += reactor->run_one(); + + n_work_attempted += n_deq_attempted; + n_work_done += n_deq_done; + n_delivered += n_deq_done; + } + + log && log(xtag("i", i), + xtag("n", n), + xtag("n_work_attempted", n_work_attempted), + xtag("n_work_done", n_work_done), + xtag("n_enq", n_enq), + xtag("n_deq_attempted", n_deq_attempted), + xtag("n_deq_done", n_deq_done)); + + if ((i == seq.u1v().size()) /*no more enqueues planned*/ + && (n_work_attempted > 0) + && (n_work_done == 0)) + { + /* expect incremental progress every iteration; + * want unit test to always terminate + */ + break; + } + } + + REQUIRE_ORFAIL(ok_flag, catch_flag, i == n); + REQUIRE_ORFAIL(ok_flag, catch_flag, n_delivered == n); + + /* check events delivered 1:1 and in order */ + for (std::size_t i=0; i seed; + auto rgen = xo::rng::xoshiro256ss(seed); + + for (std::size_t n = 4; n <= 1024; n *= 2) { + bool ok_flag = false; + + for (std::uint32_t attention = 0; !ok_flag && (attention < 2); ++attention) { + ok_flag = true; + + /* attention=0: + * - no logging + * - detect assertion failures, but don't report them to catch + * attention=1: + * - only runs if failure detected with attention=0 + * - full logging + * - report to catch + */ + + bool debug_flag = (attention == 1); + + ok_flag &= run_polling1_test(n, debug_flag, &rgen); + } + } + + } /*TEST_CASE(polling1)*/ + } /*namespace ut*/ + +} /*namespace xo*/ + + +/* end PollingReactor.test.cpp */ diff --git a/utest/Sink.test.cpp b/utest/Sink.test.cpp new file mode 100644 index 00000000..160530a0 --- /dev/null +++ b/utest/Sink.test.cpp @@ -0,0 +1,100 @@ +/* @file Sink.test.cpp */ + +#include "xo/reactor/PollingReactor.hpp" +#include "xo/reactor/Sink.hpp" +#include "xo/indentlog/print/pair.hpp" +#include "catch2/catch.hpp" + +namespace xo { + using xo::reactor::Reactor; + using xo::reactor::PollingReactor; + using xo::reactor::AbstractSink; + using xo::reactor::Sink1; + using xo::reactor::SinkEndpoint; + using xo::reactor::SinkToConsole; + using xo::time::utc_nanos; + using xo::ref::rp; + + namespace { + class TestSink : public SinkEndpoint { + public: + TestSink() = default; + + virtual uint32_t n_in_ev() const override { return 0; } + virtual bool allow_volatile_source() const override { return true; } + virtual void notify_ev(int const & ev) override {} + virtual void display(std::ostream & os) const override { os << ""; } + }; /*TestSink*/ + + class TestSink2 : public SinkEndpoint { + public: + TestSink2() = default; + + virtual uint32_t n_in_ev() const override { return 0; } + virtual bool allow_volatile_source() const override { return true; } + virtual void notify_ev(utc_nanos const & ev) override {} + virtual void display(std::ostream & os) const override { os << ""; } + }; /*TestSink2*/ + + using TestSink3 = SinkToConsole>; + } /*namespace*/ + + namespace ut { + TEST_CASE("sink-cast", "[reactor][sink]") { + rp test_sink = new TestSink(); + rp sink = test_sink; + + TestSink * cast_sink = dynamic_cast(sink.get()); + + REQUIRE(test_sink.get() == cast_sink); + + Sink1 * int_sink = dynamic_cast *>(sink.get()); + + REQUIRE(test_sink.get() == int_sink); + + rp> int_sink2 + = Sink1::require_native("TEST_CASE(sink-cast)", sink.get()); + + REQUIRE(test_sink.get() == int_sink2.get()); + } /*TEST_CASE(sink-cast)*/ + + TEST_CASE("sink-cast2", "[reactor]") { + rp test_sink = new TestSink2(); + rp sink = test_sink; + + TestSink2 * cast_sink = dynamic_cast(sink.get()); + + REQUIRE(test_sink.get() == cast_sink); + + Sink1 * dt_sink = dynamic_cast *>(sink.get()); + + REQUIRE(test_sink.get() == dt_sink); + + rp> dt_sink2 + = Sink1::require_native("TEST_CASE(sink-cast2)", sink.get()); + + REQUIRE(test_sink.get() == dt_sink2.get()); + } /*TEST_CASE(sink-cast2)*/ + + TEST_CASE("sink-cast3", "[reactor]") { + rp test_sink = new TestSink3(); + rp sink = test_sink; + + TestSink3 * cast_sink = dynamic_cast(sink.get()); + + REQUIRE(test_sink.get() == cast_sink); + + Sink1> * ev_sink + = dynamic_cast> *>(sink.get()); + + REQUIRE(test_sink.get() == ev_sink); + + rp>> ev_sink2 + = Sink1>::require_native("TEST_CASE(sink-cast3)", sink.get()); + + REQUIRE(test_sink.get() == ev_sink2.get()); + } /*TEST_CASE(sink-cast3)*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end Sink.test.cpp */ diff --git a/utest/reactor_utest_main.cpp b/utest/reactor_utest_main.cpp new file mode 100644 index 00000000..c3b80295 --- /dev/null +++ b/utest/reactor_utest_main.cpp @@ -0,0 +1,6 @@ +/* @file reactor_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end reactor_utest_main.cpp */ From 30a524b22cf63dfd25381986fb45231ed70d0a04 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 17:51:31 -0400 Subject: [PATCH 0213/2524] build: install include tree --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1735ddc8..5b0079d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,4 +45,9 @@ add_subdirectory(src/callback) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) +# ---------------------------------------------------------------- +# install .hpp files + +xo_install_include_tree() + # end CMakeLists.txt From 4c825252ed85c8ebeeb980e540ef3a6bdfb718d8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 18:03:20 -0400 Subject: [PATCH 0214/2524] xo-cmake: + xo_add_headeronly_library() --- cmake/xo_cxx.cmake | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index f0e2e29b..13ee6b9a 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -108,6 +108,14 @@ macro(xo_add_shared_library target targetversion soversion sources) xo_install_library2(${target}) endmacro() +# ---------------------------------------------------------------- +# use this for a header-only library +# +macro(xo_add_headeronly_library target) + add_library(${target} INTERFACE) + xo_include_headeronly_options2(${target}) +endmacro() + # ---------------------------------------------------------------- # use this in subdirs that compile c++ code. # do not use for header-only subsystems; see xo_include_headeronly_options2() From 1c648d70c3c3a226f193abaa521149899a2a1c6f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 18:03:40 -0400 Subject: [PATCH 0215/2524] xo-cmake: + xo_install_library3() --- cmake/xo_cxx.cmake | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 13ee6b9a..64ec2822 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -184,6 +184,20 @@ macro(xo_install_library2 target) ) endmacro() +macro(xo_install_library3 target projectTargets) + install( + TARGETS ${target} + EXPORT ${projectTargets} + 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 + ) + + xo_install_include_tree() +endmacro() + # ---------------------------------------------------------------- # for projectname=foo, require: From 886ad07eb332125962cd712e49c20d9f045203a9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 18:26:49 -0400 Subject: [PATCH 0216/2524] initial implementation --- CMakeLists.txt | 50 ++++++++++++++ README.md | 28 ++++++++ cmake/webutilConfig.cmake.in | 4 ++ include/xo/webutil/Alist.hpp | 43 ++++++++++++ include/xo/webutil/HttpEndpointDescr.hpp | 76 +++++++++++++++++++++ include/xo/webutil/StreamEndpointDescr.hpp | 78 ++++++++++++++++++++++ 6 files changed, 279 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/webutilConfig.cmake.in create mode 100644 include/xo/webutil/Alist.hpp create mode 100644 include/xo/webutil/HttpEndpointDescr.hpp create mode 100644 include/xo/webutil/StreamEndpointDescr.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..dcce42b2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,50 @@ +# xo-webutil/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(webutil VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# unit test setup (no unit tests yet, but want 'make tests' to do something) + +enable_testing() +# activate code coverage for all executables + libraries (when -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. +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) + +# ---------------------------------------------------------------- + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +# header-only library. +# see [[https://stackoverflow.com/questions/47718485/install-and-export-interface-only-library-cmake]] +# +xo_add_headeronly_library(webutil) + +# ---------------------------------------------------------------- +# standard install + +xo_install_library3(webutil ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install bespoke targets, if any + +#install(TARGETS example DESTINATION bin/xo-webutil/example) diff --git a/README.md b/README.md new file mode 100644 index 00000000..a8eb8c77 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# webutil library (header-only) + +# dependencies + +- xo-cmake [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) + +# clone repo +``` +$ git clone git@github.com:Rconybea/xo-webutil.git +``` + +# build and install +``` +$ cd xo-webutil +$ BUILDDIR=build # for example +$ mkdir $BUILDDIR +$ cd $BUILDDIR +$ PREFIX=/usr/local # for example +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} .. +$ make +$ make install +``` + +# LSP support +``` +$ cd xo-webutil +$ ln -s $BUILDDIR/compile_commands.json +``` diff --git a/cmake/webutilConfig.cmake.in b/cmake/webutilConfig.cmake.in new file mode 100644 index 00000000..c44284f2 --- /dev/null +++ b/cmake/webutilConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/webutilTargets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/webutil/Alist.hpp b/include/xo/webutil/Alist.hpp new file mode 100644 index 00000000..80fcaa20 --- /dev/null +++ b/include/xo/webutil/Alist.hpp @@ -0,0 +1,43 @@ +/* file Alist.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include +#include +#include + +namespace xo { + namespace web { + /* assocation list, maps strings to strings + * use this for arguments to dynamic-endpoint-callbacks + */ + class Alist { + public: + Alist() = default; + + /* lookup association by name */ + std::string_view lookup(std::string n) const { + for (auto const & ix : this->assoc_v_) { + if (ix.first == n) { + return ix.second; + } + } + + return ""; + } /*lookup*/ + + void push_back(std::string n, std::string v) { + this->assoc_v_.push_back(std::make_pair(std::move(n), std::move(v))); + } + + private: + std::vector> assoc_v_; + }; /*Alist*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end Alist.hpp */ diff --git a/include/xo/webutil/HttpEndpointDescr.hpp b/include/xo/webutil/HttpEndpointDescr.hpp new file mode 100644 index 00000000..79a7070f --- /dev/null +++ b/include/xo/webutil/HttpEndpointDescr.hpp @@ -0,0 +1,76 @@ +/* file EndpointDescr.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "web_util/Alist.hpp" +#include "refcnt/Refcounted.hpp" +#include "indentlog/print/tag.hpp" +#include "indentlog/print/tostr.hpp" +#include + +namespace xo { + namespace web { + /* a function that can deliver http content on demand. */ + using HttpEndpointFn = std::function; + + /* describes an http endpoint -- + * this comprises: + * - a uri pattern. + * - a function that can deliver http content on demand + */ + class HttpEndpointDescr { + public: + HttpEndpointDescr(std::string uri_pattern, + HttpEndpointFn endpoint_fn) + : uri_pattern_{std::move(uri_pattern)}, + endpoint_fn_{std::move(endpoint_fn)} + {} + + std::string const & uri_pattern() const { return uri_pattern_; } + HttpEndpointFn const & endpoint_fn() const { return endpoint_fn_; } + + void display(std::ostream & os) const { + using xo::xtag; + + os << ""; + } /*display*/ + + std::string display_string() const { return xo::tostr(*this); } + + private: + /* unique pattern in URI-space for this endpoint. + * for example + * .uri_pattern = /stem/${foo}/${bar} + * means this endpoint generates contents for uri's + * /stem/apple/banana + * /stem/aphid/green + * but not for + * /stem/apple/banana/carrot + */ + std::string uri_pattern_; + /* a function that can construct http output on demand + * .endpoint_fn(uri, alist, &os) + * writes http output to os. output is parameterized + * by name-value pairs in alist, and is prepared on behalf + * of .uri_pattern + * alist will report name-value pairs for each variable that + * appears in .uri_pattern (surrounded by ${..}) + */ + HttpEndpointFn endpoint_fn_; + }; /*HttpEndpointDescr*/ + + inline std::ostream & + operator<<(std::ostream & os, HttpEndpointDescr const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end EndpointDescr.hpp */ diff --git a/include/xo/webutil/StreamEndpointDescr.hpp b/include/xo/webutil/StreamEndpointDescr.hpp new file mode 100644 index 00000000..fe86b135 --- /dev/null +++ b/include/xo/webutil/StreamEndpointDescr.hpp @@ -0,0 +1,78 @@ +/* file StreamEndpointDescr.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "web_util/Alist.hpp" +#include "callback/CallbackSet.hpp" +#include "refcnt/Refcounted.hpp" +#include "indentlog/print/tag.hpp" +#include "indentlog/print/tostr.hpp" +#include + +namespace xo { + namespace reactor { class AbstractSink; } + + namespace web { + /* a function that creates an event subscription */ + using StreamSubscribeFn = std::function const & ws_sink)>; + using StreamUnsubscribeFn = std::function; + + /* describes a stream endpoint + * this comprises + * - a uri pattern (matches stream name) + * - a function that establishes subscription + * (by attaching supplied WebsocketSink to an event source) + */ + class StreamEndpointDescr { + public: + StreamEndpointDescr(std::string uri_pattern, + StreamSubscribeFn subscribe_fn, + StreamUnsubscribeFn unsubscribe_fn) + : uri_pattern_{std::move(uri_pattern)}, + subscribe_fn_{std::move(subscribe_fn)}, + unsubscribe_fn_{std::move(unsubscribe_fn)} {} + + std::string const & uri_pattern() const { return uri_pattern_; } + StreamSubscribeFn const & subscribe_fn() const { return subscribe_fn_; } + StreamUnsubscribeFn const & unsubscribe_fn() const { return unsubscribe_fn_; } + + void display(std::ostream & os) const { + using xo::xtag; + + os << ""; + } /*display*/ + + std::string display_string() const { return xo::tostr(*this); } + + private: + /* unique pattern in URI-space for this endpoint + * for example + * .uri_pattern = /stem/${foo}/${bar} + * means this endpoint generates contents for uri's + * /stem/apple/banana + * /stem/aphid/green + * but not for + * /stem/apple/banana/carrot + */ + std::string uri_pattern_; + /* a function that subscribes to an event stream + * (by attaching a websocket sink) + */ + StreamSubscribeFn subscribe_fn_; + /* reverses effect of a particular call to .subscribe_fn */ + StreamUnsubscribeFn unsubscribe_fn_; + }; /*StreamEndpointDescr*/ + + inline std::ostream & + operator<<(std::ostream & os, StreamEndpointDescr const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end StreamEndpointDescr.hpp */ From 8759357ec7730cf3eca5941dfe6d261d4bf08c6a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 18:27:14 -0400 Subject: [PATCH 0217/2524] + .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..abafa2ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# typical build directories +build From 9a23b29c12d53f773d6e46a45e7b3e1f95633821 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 18:43:04 -0400 Subject: [PATCH 0218/2524] cosmetic: formatting --- include/xo/webutil/Alist.hpp | 50 +++++----- include/xo/webutil/HttpEndpointDescr.hpp | 102 ++++++++++---------- include/xo/webutil/StreamEndpointDescr.hpp | 104 ++++++++++----------- 3 files changed, 128 insertions(+), 128 deletions(-) diff --git a/include/xo/webutil/Alist.hpp b/include/xo/webutil/Alist.hpp index 80fcaa20..ccb317b3 100644 --- a/include/xo/webutil/Alist.hpp +++ b/include/xo/webutil/Alist.hpp @@ -10,34 +10,34 @@ #include namespace xo { - namespace web { - /* assocation list, maps strings to strings - * use this for arguments to dynamic-endpoint-callbacks - */ - class Alist { - public: - Alist() = default; - - /* lookup association by name */ - std::string_view lookup(std::string n) const { - for (auto const & ix : this->assoc_v_) { - if (ix.first == n) { - return ix.second; - } - } + namespace web { + /* assocation list, maps strings to strings + * use this for arguments to dynamic-endpoint-callbacks + */ + class Alist { + public: + Alist() = default; - return ""; - } /*lookup*/ + /* lookup association by name */ + std::string_view lookup(std::string n) const { + for (auto const & ix : this->assoc_v_) { + if (ix.first == n) { + return ix.second; + } + } - void push_back(std::string n, std::string v) { - this->assoc_v_.push_back(std::make_pair(std::move(n), std::move(v))); - } + return ""; + } /*lookup*/ - private: - std::vector> assoc_v_; - }; /*Alist*/ - - } /*namespace web*/ + void push_back(std::string n, std::string v) { + this->assoc_v_.push_back(std::make_pair(std::move(n), std::move(v))); + } + + private: + std::vector> assoc_v_; + }; /*Alist*/ + + } /*namespace web*/ } /*namespace xo*/ /* end Alist.hpp */ diff --git a/include/xo/webutil/HttpEndpointDescr.hpp b/include/xo/webutil/HttpEndpointDescr.hpp index 79a7070f..97ff4cee 100644 --- a/include/xo/webutil/HttpEndpointDescr.hpp +++ b/include/xo/webutil/HttpEndpointDescr.hpp @@ -12,65 +12,65 @@ #include namespace xo { - namespace web { - /* a function that can deliver http content on demand. */ - using HttpEndpointFn = std::function; + namespace web { + /* a function that can deliver http content on demand. */ + using HttpEndpointFn = std::function; - /* describes an http endpoint -- - * this comprises: - * - a uri pattern. - * - a function that can deliver http content on demand - */ - class HttpEndpointDescr { - public: - HttpEndpointDescr(std::string uri_pattern, - HttpEndpointFn endpoint_fn) - : uri_pattern_{std::move(uri_pattern)}, - endpoint_fn_{std::move(endpoint_fn)} - {} + /* describes an http endpoint -- + * this comprises: + * - a uri pattern. + * - a function that can deliver http content on demand + */ + class HttpEndpointDescr { + public: + HttpEndpointDescr(std::string uri_pattern, + HttpEndpointFn endpoint_fn) + : uri_pattern_{std::move(uri_pattern)}, + endpoint_fn_{std::move(endpoint_fn)} + {} - std::string const & uri_pattern() const { return uri_pattern_; } - HttpEndpointFn const & endpoint_fn() const { return endpoint_fn_; } + std::string const & uri_pattern() const { return uri_pattern_; } + HttpEndpointFn const & endpoint_fn() const { return endpoint_fn_; } - void display(std::ostream & os) const { - using xo::xtag; + void display(std::ostream & os) const { + using xo::xtag; - os << ""; - } /*display*/ + os << ""; + } /*display*/ - std::string display_string() const { return xo::tostr(*this); } + std::string display_string() const { return xo::tostr(*this); } - private: - /* unique pattern in URI-space for this endpoint. - * for example - * .uri_pattern = /stem/${foo}/${bar} - * means this endpoint generates contents for uri's - * /stem/apple/banana - * /stem/aphid/green - * but not for - * /stem/apple/banana/carrot - */ - std::string uri_pattern_; - /* a function that can construct http output on demand - * .endpoint_fn(uri, alist, &os) - * writes http output to os. output is parameterized - * by name-value pairs in alist, and is prepared on behalf - * of .uri_pattern - * alist will report name-value pairs for each variable that - * appears in .uri_pattern (surrounded by ${..}) - */ - HttpEndpointFn endpoint_fn_; - }; /*HttpEndpointDescr*/ + private: + /* unique pattern in URI-space for this endpoint. + * for example + * .uri_pattern = /stem/${foo}/${bar} + * means this endpoint generates contents for uri's + * /stem/apple/banana + * /stem/aphid/green + * but not for + * /stem/apple/banana/carrot + */ + std::string uri_pattern_; + /* a function that can construct http output on demand + * .endpoint_fn(uri, alist, &os) + * writes http output to os. output is parameterized + * by name-value pairs in alist, and is prepared on behalf + * of .uri_pattern + * alist will report name-value pairs for each variable that + * appears in .uri_pattern (surrounded by ${..}) + */ + HttpEndpointFn endpoint_fn_; + }; /*HttpEndpointDescr*/ - inline std::ostream & - operator<<(std::ostream & os, HttpEndpointDescr const & x) { - x.display(os); - return os; - } /*operator<<*/ + inline std::ostream & + operator<<(std::ostream & os, HttpEndpointDescr const & x) { + x.display(os); + return os; + } /*operator<<*/ - } /*namespace web*/ + } /*namespace web*/ } /*namespace xo*/ /* end EndpointDescr.hpp */ diff --git a/include/xo/webutil/StreamEndpointDescr.hpp b/include/xo/webutil/StreamEndpointDescr.hpp index fe86b135..1b361610 100644 --- a/include/xo/webutil/StreamEndpointDescr.hpp +++ b/include/xo/webutil/StreamEndpointDescr.hpp @@ -5,7 +5,7 @@ #pragma once -#include "web_util/Alist.hpp" +#include "Alist.hpp" #include "callback/CallbackSet.hpp" #include "refcnt/Refcounted.hpp" #include "indentlog/print/tag.hpp" @@ -13,66 +13,66 @@ #include namespace xo { - namespace reactor { class AbstractSink; } + namespace reactor { class AbstractSink; } - namespace web { - /* a function that creates an event subscription */ - using StreamSubscribeFn = std::function const & ws_sink)>; - using StreamUnsubscribeFn = std::function; + namespace web { + /* a function that creates an event subscription */ + using StreamSubscribeFn = std::function const & ws_sink)>; + using StreamUnsubscribeFn = std::function; - /* describes a stream endpoint - * this comprises - * - a uri pattern (matches stream name) - * - a function that establishes subscription - * (by attaching supplied WebsocketSink to an event source) - */ - class StreamEndpointDescr { - public: - StreamEndpointDescr(std::string uri_pattern, - StreamSubscribeFn subscribe_fn, - StreamUnsubscribeFn unsubscribe_fn) - : uri_pattern_{std::move(uri_pattern)}, - subscribe_fn_{std::move(subscribe_fn)}, - unsubscribe_fn_{std::move(unsubscribe_fn)} {} + /* describes a stream endpoint + * this comprises + * - a uri pattern (matches stream name) + * - a function that establishes subscription + * (by attaching supplied WebsocketSink to an event source) + */ + class StreamEndpointDescr { + public: + StreamEndpointDescr(std::string uri_pattern, + StreamSubscribeFn subscribe_fn, + StreamUnsubscribeFn unsubscribe_fn) + : uri_pattern_{std::move(uri_pattern)}, + subscribe_fn_{std::move(subscribe_fn)}, + unsubscribe_fn_{std::move(unsubscribe_fn)} {} - std::string const & uri_pattern() const { return uri_pattern_; } - StreamSubscribeFn const & subscribe_fn() const { return subscribe_fn_; } - StreamUnsubscribeFn const & unsubscribe_fn() const { return unsubscribe_fn_; } + std::string const & uri_pattern() const { return uri_pattern_; } + StreamSubscribeFn const & subscribe_fn() const { return subscribe_fn_; } + StreamUnsubscribeFn const & unsubscribe_fn() const { return unsubscribe_fn_; } - void display(std::ostream & os) const { - using xo::xtag; + void display(std::ostream & os) const { + using xo::xtag; - os << ""; - } /*display*/ + os << ""; + } /*display*/ - std::string display_string() const { return xo::tostr(*this); } + std::string display_string() const { return xo::tostr(*this); } - private: - /* unique pattern in URI-space for this endpoint - * for example - * .uri_pattern = /stem/${foo}/${bar} - * means this endpoint generates contents for uri's - * /stem/apple/banana - * /stem/aphid/green - * but not for - * /stem/apple/banana/carrot - */ - std::string uri_pattern_; - /* a function that subscribes to an event stream - * (by attaching a websocket sink) - */ - StreamSubscribeFn subscribe_fn_; - /* reverses effect of a particular call to .subscribe_fn */ - StreamUnsubscribeFn unsubscribe_fn_; - }; /*StreamEndpointDescr*/ + private: + /* unique pattern in URI-space for this endpoint + * for example + * .uri_pattern = /stem/${foo}/${bar} + * means this endpoint generates contents for uri's + * /stem/apple/banana + * /stem/aphid/green + * but not for + * /stem/apple/banana/carrot + */ + std::string uri_pattern_; + /* a function that subscribes to an event stream + * (by attaching a websocket sink) + */ + StreamSubscribeFn subscribe_fn_; + /* reverses effect of a particular call to .subscribe_fn */ + StreamUnsubscribeFn unsubscribe_fn_; + }; /*StreamEndpointDescr*/ - inline std::ostream & - operator<<(std::ostream & os, StreamEndpointDescr const & x) { - x.display(os); - return os; - } /*operator<<*/ + inline std::ostream & + operator<<(std::ostream & os, StreamEndpointDescr const & x) { + x.display(os); + return os; + } /*operator<<*/ - } /*namespace web*/ + } /*namespace web*/ } /*namespace xo*/ /* end StreamEndpointDescr.hpp */ From 38b8f34b2884768172dfd2394b5f44694e31ff54 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 19:19:44 -0400 Subject: [PATCH 0219/2524] refactor: move impl into .cpp + fix include paths --- CMakeLists.txt | 19 ++++++++------- cmake/webutilConfig.cmake.in | 2 ++ include/xo/webutil/Alist.hpp | 14 ++--------- include/xo/webutil/HttpEndpointDescr.hpp | 20 +++++----------- include/xo/webutil/StreamEndpointDescr.hpp | 19 ++++----------- src/webutil/Alist.cpp | 28 ++++++++++++++++++++++ src/webutil/CMakeLists.txt | 14 +++++++++++ src/webutil/HttpEndpointDescr.cpp | 27 +++++++++++++++++++++ src/webutil/StreamEndpointDescr.cpp | 28 ++++++++++++++++++++++ 9 files changed, 122 insertions(+), 49 deletions(-) create mode 100644 src/webutil/Alist.cpp create mode 100644 src/webutil/CMakeLists.txt create mode 100644 src/webutil/HttpEndpointDescr.cpp create mode 100644 src/webutil/StreamEndpointDescr.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index dcce42b2..04cc53f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,20 +24,19 @@ add_code_coverage() add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) # ---------------------------------------------------------------- +# common c++ settings + +# PROJECT_CXX_FLAGS: bespoke for this project - usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) xo_toplevel_compile_options() # ---------------------------------------------------------------- +# sources -# header-only library. -# see [[https://stackoverflow.com/questions/47718485/install-and-export-interface-only-library-cmake]] -# -xo_add_headeronly_library(webutil) - -# ---------------------------------------------------------------- -# standard install - -xo_install_library3(webutil ${PROJECT_NAME}Targets) +add_subdirectory(src/webutil) # ---------------------------------------------------------------- # provide find_package() support @@ -47,4 +46,6 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- # install bespoke targets, if any +xo_install_include_tree() + #install(TARGETS example DESTINATION bin/xo-webutil/example) diff --git a/cmake/webutilConfig.cmake.in b/cmake/webutilConfig.cmake.in index c44284f2..79a9df3d 100644 --- a/cmake/webutilConfig.cmake.in +++ b/cmake/webutilConfig.cmake.in @@ -1,4 +1,6 @@ @PACKAGE_INIT@ +include(CMakeFindDependencyMacro) +find_dependency(callback) include("${CMAKE_CURRENT_LIST_DIR}/webutilTargets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/include/xo/webutil/Alist.hpp b/include/xo/webutil/Alist.hpp index ccb317b3..4bc5a305 100644 --- a/include/xo/webutil/Alist.hpp +++ b/include/xo/webutil/Alist.hpp @@ -19,19 +19,9 @@ namespace xo { Alist() = default; /* lookup association by name */ - std::string_view lookup(std::string n) const { - for (auto const & ix : this->assoc_v_) { - if (ix.first == n) { - return ix.second; - } - } + std::string_view lookup(std::string n) const; - return ""; - } /*lookup*/ - - void push_back(std::string n, std::string v) { - this->assoc_v_.push_back(std::make_pair(std::move(n), std::move(v))); - } + void push_back(std::string n, std::string v); private: std::vector> assoc_v_; diff --git a/include/xo/webutil/HttpEndpointDescr.hpp b/include/xo/webutil/HttpEndpointDescr.hpp index 97ff4cee..f3598cbb 100644 --- a/include/xo/webutil/HttpEndpointDescr.hpp +++ b/include/xo/webutil/HttpEndpointDescr.hpp @@ -5,11 +5,10 @@ #pragma once -#include "web_util/Alist.hpp" -#include "refcnt/Refcounted.hpp" -#include "indentlog/print/tag.hpp" -#include "indentlog/print/tostr.hpp" +#include "Alist.hpp" +#include "xo/refcnt/Refcounted.hpp" #include +#include namespace xo { namespace web { @@ -26,21 +25,14 @@ namespace xo { class HttpEndpointDescr { public: HttpEndpointDescr(std::string uri_pattern, - HttpEndpointFn endpoint_fn) - : uri_pattern_{std::move(uri_pattern)}, - endpoint_fn_{std::move(endpoint_fn)} - {} + HttpEndpointFn endpoint_fn); std::string const & uri_pattern() const { return uri_pattern_; } HttpEndpointFn const & endpoint_fn() const { return endpoint_fn_; } - void display(std::ostream & os) const { - using xo::xtag; + void display(std::ostream & os) const; - os << ""; - } /*display*/ - - std::string display_string() const { return xo::tostr(*this); } + std::string display_string() const; private: /* unique pattern in URI-space for this endpoint. diff --git a/include/xo/webutil/StreamEndpointDescr.hpp b/include/xo/webutil/StreamEndpointDescr.hpp index 1b361610..78053008 100644 --- a/include/xo/webutil/StreamEndpointDescr.hpp +++ b/include/xo/webutil/StreamEndpointDescr.hpp @@ -6,10 +6,8 @@ #pragma once #include "Alist.hpp" -#include "callback/CallbackSet.hpp" -#include "refcnt/Refcounted.hpp" -#include "indentlog/print/tag.hpp" -#include "indentlog/print/tostr.hpp" +#include "xo/callback/CallbackSet.hpp" +#include "xo/refcnt/Refcounted.hpp" #include namespace xo { @@ -30,22 +28,15 @@ namespace xo { public: StreamEndpointDescr(std::string uri_pattern, StreamSubscribeFn subscribe_fn, - StreamUnsubscribeFn unsubscribe_fn) - : uri_pattern_{std::move(uri_pattern)}, - subscribe_fn_{std::move(subscribe_fn)}, - unsubscribe_fn_{std::move(unsubscribe_fn)} {} + StreamUnsubscribeFn unsubscribe_fn); std::string const & uri_pattern() const { return uri_pattern_; } StreamSubscribeFn const & subscribe_fn() const { return subscribe_fn_; } StreamUnsubscribeFn const & unsubscribe_fn() const { return unsubscribe_fn_; } - void display(std::ostream & os) const { - using xo::xtag; + void display(std::ostream & os) const; - os << ""; - } /*display*/ - - std::string display_string() const { return xo::tostr(*this); } + std::string display_string() const; private: /* unique pattern in URI-space for this endpoint diff --git a/src/webutil/Alist.cpp b/src/webutil/Alist.cpp new file mode 100644 index 00000000..73905106 --- /dev/null +++ b/src/webutil/Alist.cpp @@ -0,0 +1,28 @@ +/* @file Alist.cpp */ + +#include "Alist.hpp" + +namespace xo { + namespace web { + /* lookup association by name */ + std::string_view + Alist::lookup(std::string n) const { + for (auto const & ix : this->assoc_v_) { + if (ix.first == n) { + return ix.second; + } + } + + return ""; + } /*lookup*/ + + void + Alist::push_back(std::string n, std::string v) { + this->assoc_v_.push_back(std::make_pair(std::move(n), std::move(v))); + } + } /*namespace web*/ +} /*namespace xo*/ + + + +/* end Alist.cpp */ diff --git a/src/webutil/CMakeLists.txt b/src/webutil/CMakeLists.txt new file mode 100644 index 00000000..57f96726 --- /dev/null +++ b/src/webutil/CMakeLists.txt @@ -0,0 +1,14 @@ +# webutil/CMakeLists.txt + +set(SELF_LIB webutil) +set(SELF_SRCS StreamEndpointDescr.cpp HttpEndpointDescr.cpp Alist.cpp) + +# reminder: can't be header-only library, because depends on non-header-only callback (bc of non-header-only refcnt) +xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies + +xo_dependency(${SELF_LIB} callback) + +# end CMakeLists.txt diff --git a/src/webutil/HttpEndpointDescr.cpp b/src/webutil/HttpEndpointDescr.cpp new file mode 100644 index 00000000..fb9ab261 --- /dev/null +++ b/src/webutil/HttpEndpointDescr.cpp @@ -0,0 +1,27 @@ +/* @file HttpEndpointDescr.cpp */ + +#include "HttpEndpointDescr.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/tostr.hpp" + +namespace xo { + namespace web { + HttpEndpointDescr::HttpEndpointDescr(std::string uri_pattern, + HttpEndpointFn endpoint_fn) + : uri_pattern_{std::move(uri_pattern)}, + endpoint_fn_{std::move(endpoint_fn)} + {} + + void + HttpEndpointDescr::display(std::ostream & os) const { + os << ""; + } /*display*/ + + std::string + HttpEndpointDescr::display_string() const { return tostr(*this); } + } /*namespace web*/ + +} /*namespace xo*/ + + +/* end HttpEndpointDescr.cpp */ diff --git a/src/webutil/StreamEndpointDescr.cpp b/src/webutil/StreamEndpointDescr.cpp new file mode 100644 index 00000000..03c2e961 --- /dev/null +++ b/src/webutil/StreamEndpointDescr.cpp @@ -0,0 +1,28 @@ +/* @file StreamEndpointDescr.cpp */ + +#include "StreamEndpointDescr.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/tostr.hpp" + +namespace xo { + namespace web { + StreamEndpointDescr::StreamEndpointDescr(std::string uri_pattern, + StreamSubscribeFn subscribe_fn, + StreamUnsubscribeFn unsubscribe_fn) + : uri_pattern_{std::move(uri_pattern)}, + subscribe_fn_{std::move(subscribe_fn)}, + unsubscribe_fn_{std::move(unsubscribe_fn)} + {} + + void + StreamEndpointDescr::display(std::ostream & os) const { + os << ""; + } /*display*/ + + std::string + StreamEndpointDescr::display_string() const { return tostr(*this); } + } /*namespace web*/ +} /*namespace xo*/ + + +/* end StreamEndpointDescr.cpp */ From 937109896c1fac15779ae94e27bd18d93493cc5e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 19:20:36 -0400 Subject: [PATCH 0220/2524] ext .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index abafa2ca..3e50f879 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ +# lsp keeps state here +.cache +# symlink -> build/compile_commands.json should be created manually +compile_commands.json # typical build directories build From 12d38a7e4a87b08ddfbf0590e6b6c92929b211a1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 20:12:16 -0400 Subject: [PATCH 0221/2524] build: + reflect dependency --- README.md | 9 ++++++--- cmake/printjsonConfig.cmake.in | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ab049018..99492884 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # printjson library -### build + install +# build + install ``` $ cd xo-printjson $ mkdir build @@ -11,15 +11,18 @@ $ make $ make install ``` -### build for unit test coverage +# build for unit test coverage ``` $ cd xo-printjson $ mkdir ccov $ cd ccov $ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +$ make # builds executables +$ make ccov # runs instrumented unit tests +$ make ccov-all # generates lcov report ``` -### LSP support +# LSP support ``` $ cd xo-printjson $ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree diff --git a/cmake/printjsonConfig.cmake.in b/cmake/printjsonConfig.cmake.in index c7d8974c..e7f7f1be 100644 --- a/cmake/printjsonConfig.cmake.in +++ b/cmake/printjsonConfig.cmake.in @@ -1,6 +1,6 @@ @PACKAGE_INIT@ -#include(CMakeFindDependencyMacro) -#find_dependency(refcnt) +include(CMakeFindDependencyMacro) +find_dependency(reflect) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From daa09aff863715b4b56083544a9977e9239970ae Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 11 Oct 2023 20:32:18 -0400 Subject: [PATCH 0222/2524] reactor: auto-initialization hooks --- include/xo/reactor/Reactor.hpp | 3 +++ include/xo/reactor/init_reactor.hpp | 12 ++++----- src/reactor/Reactor.cpp | 40 ++++++++++++++++++----------- utest/PollingReactor.test.cpp | 7 +++++ 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/include/xo/reactor/Reactor.hpp b/include/xo/reactor/Reactor.hpp index 453312a4..2b265b0b 100644 --- a/include/xo/reactor/Reactor.hpp +++ b/include/xo/reactor/Reactor.hpp @@ -61,6 +61,9 @@ namespace xo { */ void run() { this->run_n(-1); } + protected: + Reactor(); + private: /* control logging verbosity */ log_level loglevel_; diff --git a/include/xo/reactor/init_reactor.hpp b/include/xo/reactor/init_reactor.hpp index 5977d7ad..3e5e51bb 100644 --- a/include/xo/reactor/init_reactor.hpp +++ b/include/xo/reactor/init_reactor.hpp @@ -8,13 +8,13 @@ #include "xo/subsys/Subsystem.hpp" namespace xo { - enum S_reactor_tag {}; + enum S_reactor_tag {}; - template<> - struct InitSubsys { - static void init(); - static InitEvidence require(); - }; + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; } /*namespace xo*/ /* end init_reactor.hpp */ diff --git a/src/reactor/Reactor.cpp b/src/reactor/Reactor.cpp index 2ef80e46..199ec562 100644 --- a/src/reactor/Reactor.cpp +++ b/src/reactor/Reactor.cpp @@ -4,23 +4,33 @@ */ #include "Reactor.hpp" +#include "init_reactor.hpp" +#include "xo/subsys/Subsystem.hpp" namespace xo { - namespace reactor { - void - Reactor::run_n(int32_t n) - { - if (n == -1) { - for (;;) { - this->run_one(); - } - } else { - for (int32_t i=0; irun_one(); - } - } - } /*run_n*/ - } /*namespace reactor*/ + namespace reactor { + Reactor::Reactor() { + /* ensure reactor subsystem + deps initialized */ + + InitSubsys::require(); + + Subsystem::initialize_all(); + } + + void + Reactor::run_n(int32_t n) + { + if (n == -1) { + for (;;) { + this->run_one(); + } + } else { + for (int32_t i=0; irun_one(); + } + } + } /*run_n*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end Reactor.cpp */ diff --git a/utest/PollingReactor.test.cpp b/utest/PollingReactor.test.cpp index 39845264..97d679cb 100644 --- a/utest/PollingReactor.test.cpp +++ b/utest/PollingReactor.test.cpp @@ -1,5 +1,6 @@ /* @file PollingReactor.test.cpp */ +#include "xo/reactor/init_reactor.hpp" #include "xo/reactor/PollingReactor.hpp" #include "xo/reactor/FifoQueue.hpp" #include "xo/reactor/Sink.hpp" @@ -57,8 +58,12 @@ namespace xo { } } /*namespace*/ + static InitEvidence s_evidence = InitSubsys::require(); + namespace ut { TEST_CASE("polling0", "[reactor]") { + Subsystem::initialize_all(); + rp reactor = PollingReactor::make(); REQUIRE(reactor.get()); @@ -193,6 +198,8 @@ namespace xo { } /*run_polling1_test*/ TEST_CASE("polling1", "[reactor]") { + Subsystem::initialize_all(); + //log_config::style = function_style::streamlined; log_config::location_tab = 100; From 9adfbb822dc268756cbef93dfd7f1ec134f4010d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 12 Oct 2023 01:31:20 -0400 Subject: [PATCH 0223/2524] Reactor.run_n() returns #events delivered --- include/xo/reactor/Reactor.hpp | 2 +- src/reactor/Reactor.cpp | 10 +++++++--- utest/PollingReactor.test.cpp | 5 +---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/include/xo/reactor/Reactor.hpp b/include/xo/reactor/Reactor.hpp index 2b265b0b..229b7421 100644 --- a/include/xo/reactor/Reactor.hpp +++ b/include/xo/reactor/Reactor.hpp @@ -54,7 +54,7 @@ namespace xo { * otherwise dispatch up to n events. * n = 0 is a noop */ - void run_n(int32_t n); + std::uint64_t run_n(int32_t n); /* borrow calling thread to run indefinitely. * suitable implementation for dedicated reactor threads diff --git a/src/reactor/Reactor.cpp b/src/reactor/Reactor.cpp index 199ec562..d0803d14 100644 --- a/src/reactor/Reactor.cpp +++ b/src/reactor/Reactor.cpp @@ -17,18 +17,22 @@ namespace xo { Subsystem::initialize_all(); } - void + std::uint64_t Reactor::run_n(int32_t n) { + std::uint64_t retval = 0; + if (n == -1) { for (;;) { - this->run_one(); + retval += this->run_one(); } } else { for (int32_t i=0; irun_one(); + retval += this->run_one(); } } + + return retval; } /*run_n*/ } /*namespace reactor*/ } /*namespace xo*/ diff --git a/utest/PollingReactor.test.cpp b/utest/PollingReactor.test.cpp index 97d679cb..106acaf6 100644 --- a/utest/PollingReactor.test.cpp +++ b/utest/PollingReactor.test.cpp @@ -155,10 +155,7 @@ namespace xo { /* pick random #of elements to remove (from front of queue) */ { - - - for (std::size_t j = 0; j < n_deq_attempted; ++j) - n_deq_done += reactor->run_one(); + n_deq_done += reactor->run_n(n_deq_attempted); n_work_attempted += n_deq_attempted; n_work_done += n_deq_done; From f2c1c26f40cf8ab8882b46bd4d11e5b3b025e908 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 12 Oct 2023 09:52:17 -0400 Subject: [PATCH 0224/2524] minor: improve AbstractEventProcessor.display + include .name --- src/reactor/AbstractEventProcessor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactor/AbstractEventProcessor.cpp b/src/reactor/AbstractEventProcessor.cpp index e49cc374..4b1bcc52 100644 --- a/src/reactor/AbstractEventProcessor.cpp +++ b/src/reactor/AbstractEventProcessor.cpp @@ -78,7 +78,7 @@ namespace xo { void AbstractEventProcessor::display(std::ostream & os) const { - os << ""; + os << ""; } /*display*/ std::string From be6d36a5c80f564de7c6ea100a0a3329da55fa3c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 12 Oct 2023 10:51:25 -0400 Subject: [PATCH 0225/2524] build: print CMAKE_MODULE_PATH / CMAKE_PREFIX_PATH --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 40c2c41d..6bd4ca7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,13 @@ -# reflect/CMakeLists.txt +# xo-reflect/CMakeLists.txt cmake_minimum_required(VERSION 3.10) project(reflect VERSION 0.1) enable_language(CXX) +message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +message(STATUS "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}") + # common XO cmake macros (see proj/xo-cmake) include(xo_macros/xo_cxx) include(xo_macros/code-coverage) From d99acc979d3c018e0d21e023f4b586c7747a4192 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 16 Oct 2023 23:12:14 -0400 Subject: [PATCH 0226/2524] xo-cmake: additions to README --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a06e0291..c5c6353d 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,17 @@ Collects cmake macros to be shared across XO projects (e.g. indentlog, reflect, In some XO project `foo`: ``` $ cd build -$ cmake -DCMAKE_MODULE_PATH=/usr/local/share/cmake .. +$ PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} .. +$ make install ``` then in `foo/CMakeLists.txt`: ``` include(xo_macros/xo_cxx) ``` + +when configuring `foo`: +``` +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake path/to/foo +``` From 4c78144b472029bafcddc144641e073387dae9f3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 16 Oct 2023 23:17:24 -0400 Subject: [PATCH 0227/2524] xo-cmake: need CMAKE_INSTALL_PREFIX at least for OSX --- cmake/xo_cxx.cmake | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 64ec2822..2246fd5d 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -165,7 +165,9 @@ endmacro() # use this to install typical include file subtree # macro(xo_install_include_tree) - install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include) + install( + DIRECTORY ${PROJECT_SOURCE_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_PREFIX}/include) endmacro() # ---------------------------------------------------------------- @@ -222,13 +224,10 @@ macro(xo_export_cmake_config projectname projectversion projecttargets) ) install( EXPORT ${projecttargets} - DESTINATION lib/cmake/${projectname} - ) - install( FILES "${PROJECT_BINARY_DIR}/${projectname}ConfigVersion.cmake" "${PROJECT_BINARY_DIR}/${projectname}Config.cmake" - DESTINATION lib/cmake/${projectname} + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/${projectname} ) endmacro() From 9a1e33dfd6fea415976caed215c0a5f1993efb94 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 00:17:02 -0400 Subject: [PATCH 0228/2524] cmake: set readonly permissions on install --- CMakeLists.txt | 1 + cmake/xo_cxx.cmake | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index c018ef2d..9c9aa85f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,5 +14,6 @@ install( FILES "cmake/xo_cxx.cmake" "cmake/code-coverage.cmake" + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ DESTINATION share/cmake/${XO_PROJECT_NAME} ) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 2246fd5d..c2d64247 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -167,6 +167,7 @@ endmacro() macro(xo_install_include_tree) install( DIRECTORY ${PROJECT_SOURCE_DIR}/include/ + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ DESTINATION ${CMAKE_INSTALL_PREFIX}/include) endmacro() @@ -224,9 +225,13 @@ macro(xo_export_cmake_config projectname projectversion projecttargets) ) install( EXPORT ${projecttargets} + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/${projectname}) + install( FILES "${PROJECT_BINARY_DIR}/${projectname}ConfigVersion.cmake" "${PROJECT_BINARY_DIR}/${projectname}Config.cmake" + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/${projectname} ) endmacro() From 9227ed0ecfa5c785e023afa9227a3c9c77b3f269 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 00:19:13 -0400 Subject: [PATCH 0229/2524] build: drop default gcc-only concept diagnostic --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aef9d414..655a14a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,8 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) # c++ settings set(XO_PROJECT_NAME refcnt) -set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") # gcc-only! add_definitions(${PROJECT_CXX_FLAGS}) From f0208ec71aab7c3bd45b179700b1b0aab76babd5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 00:20:00 -0400 Subject: [PATCH 0230/2524] randomgen: compile fixes for OSX (clang 11) --- include/xo/randomgen/engine_concept.hpp | 39 ++++++++++++++++++++++++- include/xo/randomgen/random_seed.hpp | 2 +- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/include/xo/randomgen/engine_concept.hpp b/include/xo/randomgen/engine_concept.hpp index 42557b0e..38b65cd7 100644 --- a/include/xo/randomgen/engine_concept.hpp +++ b/include/xo/randomgen/engine_concept.hpp @@ -5,6 +5,37 @@ #include #include +namespace std { +#ifdef __clang__ + template < class T > + concept integral = std::is_integral_v; + + template < class T > + concept signed_integral = std::integral && std::is_signed_v; + + template < class T > + concept unsigned_integral + = std::integral && !std::signed_integral; + + template< class F, class... Args > + concept invocable + = requires(F&& f, Args&&... args) { + std::invoke(std::forward(f), std::forward(args)...); + /* not required to be equality-preserving */ + }; + + template< typename G > + concept uniform_random_bit_generator + = std::invocable + && std::unsigned_integral> + && requires { { G::min() } -> std::same_as>; + { G::max() } -> std::same_as>; + requires std::bool_constant<(G::min() < G::max())>::value; }; +#else + /* uniform_random_bit_generator provided by gcc 12.3.2 */ +#endif +} /*namespace std*/ + namespace xo { namespace rng { /* an engine generates psuedo-random bits. @@ -29,7 +60,13 @@ namespace xo { { engine.seed(r) }; { engine == engine }; { engine != engine }; - } && std::copyable && std::uniform_random_bit_generator; + } +#ifdef __clang__ + // std::copyable apparently not available in clang11 ? +#else + && std::copyable +#endif + && std::uniform_random_bit_generator; } /*namespace rng*/ } /*namespace xo*/ diff --git a/include/xo/randomgen/random_seed.hpp b/include/xo/randomgen/random_seed.hpp index 67246c01..92447b3c 100644 --- a/include/xo/randomgen/random_seed.hpp +++ b/include/xo/randomgen/random_seed.hpp @@ -24,7 +24,7 @@ namespace xo { */ template void random_seed(T * p_seed) { -# ifdef _BSD_SOURCE +# ifdef __clang__ /* NOTE: arc4random_buf() works on darwin/nix; * probably need to do something else on intel linux */ From 54413efce4bc26b9c98f6807e77efa8d812129f9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:33:52 -0400 Subject: [PATCH 0231/2524] + xo_add_shared_library3() --- cmake/xo_cxx.cmake | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 64ec2822..f55a851e 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -85,6 +85,32 @@ endmacro() # ---------------------------------------------------------------- # use this for a shared library. # +macro(xo_add_shared_library3 target projectTargets targetversion soversion sources) + add_library(${target} SHARED ${sources}) + foreach(arg IN ITEMS ${ARGN}) + #message("target=${target}; arg=${arg}") + + # to use PUBLIC here would need to split: + # $ + # $ + # but shouldn't need that, since we arrange to install includes via + # xo_include_options2() below + # + target_sources(${target} PRIVATE ${arg}) + endforeach() + set_target_properties( + ${target} + PROPERTIES + VERSION ${targetversion} + SOVERSION ${soversion}) + xo_compile_options(${target}) + xo_include_options2(${target}) + xo_install_library3(${target} ${projectTargets}) +endmacro() + +# ---------------------------------------------------------------- +# OBSOLETE. prefer xo_add_shared_library3() +# macro(xo_add_shared_library target targetversion soversion sources) add_library(${target} SHARED ${sources}) foreach(arg IN ITEMS ${ARGN}) @@ -133,11 +159,11 @@ macro(xo_include_options2 target) # target_include_directories( ${target} PUBLIC - $ # e.g. for #include "indentlog/scope.hpp" $ + $ + $ # e.g. for #include "indentlog/scope.hpp" $ # e.g. for #include "Refcounted.hpp" in refcnt/src [DEPRECATED] $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect -# $ $ # e.g. for generated .hpp files ) @@ -346,8 +372,9 @@ endmacro() # 1. a directory pyfoo/ -> library pyfoo # 2. pyfoo/pyfoo.hpp.in -> pyfoo/pyfoo.hpp # -macro(xo_pybind11_library target source_files) - configure_file(${target}.hpp.in ${target}.hpp) +macro(xo_pybind11_library target projectTargets source_files) + configure_file(${target}.hpp.in + ${PROJECT_SOURCE_DIR}/include/xo/${target}/${target}.hpp) # find_package(Python..) finds python in # /Library/Frameworks/Python.framework/... @@ -376,7 +403,7 @@ macro(xo_pybind11_library target source_files) xo_pybind11_link_flags() xo_include_options2(${target}) - xo_install_library2(${target}) + xo_install_library3(${target} ${projectTargets}) endmacro() # ---------------------------------------------------------------- From fb36eee3d0906331e85b4de00b334d3a911b9388 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:42:33 -0400 Subject: [PATCH 0232/2524] track XO_LITERAL api change --- src/Refcounted.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Refcounted.cpp b/src/Refcounted.cpp index 11c8cb62..eade7a50 100644 --- a/src/Refcounted.cpp +++ b/src/Refcounted.cpp @@ -14,7 +14,7 @@ namespace xo { void * this_ptr, Refcount * x) { - scope lscope(XO_LITERAL(verbose, self_type, method_name), + scope lscope(XO_LITERAL(log_level::verbose, self_type, method_name), "enter", xtag("this", this_ptr), xtag("x", x), From 47f86347a4842d6a5e728d76372fcb489b98fee6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:52:09 -0400 Subject: [PATCH 0233/2524] doc updates --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 3c5d34be..40032c62 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # reflection library +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-refcnt](https://github.com/Rconybea/xo-refcnt) +- [github/Rconybea/xo-subsys](https://github.com/Rconybea/xo-subsys) + ### build + install ``` $ cd reflect From 8231420c5e4dfe226f68a0d5c144d389c7cf46e2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:52:27 -0400 Subject: [PATCH 0234/2524] cosmetic: indenting --- include/xo/reflect/TypeDescr.hpp | 351 ++++++++++++++++--------------- 1 file changed, 180 insertions(+), 171 deletions(-) diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index d908a77b..94aa3aa3 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -98,204 +98,213 @@ namespace std { } /*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); } + 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*/ + 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*/ + class EqualTypeInfoRef { + public: + bool operator()(TypeInfoRef x, TypeInfoRef y) const noexcept { return TypeInfoRef::is_equal(x, y); } + }; /*EqualTypeInfoRef*/ + } /*namespace detail*/ #endif - class TypeDescrExtra; + 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; + /* 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*/ + /* test whether a type has been reflected. + * introducing this for unit testing + */ + static bool is_reflected(std::type_info const * tinfo) { + return (s_type_table_map.find(TypeInfoRef(tinfo)) + != s_type_table_map.end()); + } /*is_reflected*/ - /* NOTE: - * implementation here will be defeated if std::type_info - * objects violate ODR. This occurs with clang + 2-level namespaces, - * so important to linke with --flat_namespace defined. - * See FAQ - * [Build Issues|Q2 - dynamic_cast> fails] - */ - static TypeDescrW require(std::type_info const * tinfo, - std::string_view canonical_name, - std::unique_ptr tdextra); + /* NOTE: + * implementation here will be defeated if std::type_info + * objects violate ODR. This occurs with clang + 2-level namespaces, + * so important to linke with --flat_namespace defined. + * See FAQ + * [Build Issues|Q2 - dynamic_cast> fails] + */ + static TypeDescrW require(std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra); - /* print table of reflected types to os */ - static void print_reflected_types(std::ostream & os); + /* 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(); } + TypeId id() const { return id_; } + std::type_info const * typeinfo() const { return typeinfo_; } + std::string_view const & canonical_name() const { return canonical_name_; } + std::string_view const & short_name() const { return short_name_; } + bool complete_flag() const { return complete_flag_; } + TypeDescrExtra * tdextra() const { return tdextra_.get(); } + Metatype metatype() const { return tdextra_->metatype(); } - /* true iff the type represented by *this is the same as the type T. - * - * Warning: comparing typeinfo address can give false negatives. - * suspect this is caused by problems coalescing linker symbols - * in the clang toolchain. - */ - template - bool is_native() const { - return ((this->typeinfo() == &typeid(T)) - || (this->typeinfo()->hash_code() == typeid(T).hash_code()) - || (this->typeinfo()->name() == typeid(T).name())); - } /*is_native*/ + /* true iff the type represented by *this is the same as the type T. + * + * Warning: comparing typeinfo address can give false negatives. + * suspect this is caused by problems coalescing linker symbols + * in the clang toolchain. + */ + template + bool is_native() const { + return ((this->typeinfo() == &typeid(T)) + || (this->typeinfo()->hash_code() == typeid(T).hash_code()) + || (this->typeinfo()->name() == typeid(T).name())); + } /*is_native*/ - /* safe downcast -- like dynamic_cast<>, but does not require a source type */ - template - T * recover_native(void * address) const { - if (this->is_native()) { - return reinterpret_cast(address); - } else { - return nullptr; - } - } /*recover_native*/ + /* safe downcast -- like dynamic_cast<>, but does not require a source type */ + template + T * recover_native(void * address) const { + if (this->is_native()) { + return reinterpret_cast(address); + } else { + return nullptr; + } + } /*recover_native*/ - bool is_vector() const { return this->tdextra_->is_vector(); } - bool is_struct() const { return this->tdextra_->is_struct(); } + 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; + /* given a T-instance object, return tagged pointer with T replaced + * by the most-derived-subtype of T to which *object belongs. + * This works only for descendants of reflect::SelfTagging + */ + TaggedPtr most_derived_self_tp(void * object) const; - /* if generalized vector (std::vector, std::array, ..): - * .n_child() reports #of elements - * if struct/class: - * .n_child() reports #of instance variables (that have been reflected) - */ - uint32_t n_child(void * object) const { return this->tdextra_->n_child(object); } - TaggedPtr child_tp(uint32_t i, void * object) const; + /* if generalized vector (std::vector, std::array, ..): + * .n_child() reports #of elements + * if struct/class: + * .n_child() reports #of instance variables (that have been reflected) + */ + uint32_t n_child(void * object) const { return this->tdextra_->n_child(object); } + TaggedPtr child_tp(uint32_t i, void * object) const; - /* require: - * - .is_struct() = true - * - i in [0 .. .n_child() - 1] - */ - std::string const & struct_member_name(uint32_t i) const { - return this->tdextra_->struct_member_name(i); - } - /* fetch runtime description for i'th reflected instance variable. - * - * require: - * - .is_struct() = true - * - i in [0 .. .n_child() - 1] - */ - StructMember const & struct_member(uint32_t i) const { - StructMember const * sm = this->tdextra_->struct_member(i); + /* 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*/ + assert(sm); + return *sm; + } /*struct_member*/ - void display(std::ostream & os) const; - std::string display_string() const; + 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(); + /* mark this TypeDescr complete; + * returns the value of .complete_flag from _before_ + * this call + */ + bool mark_complete(); - /* call this once to attach extended type information to a type-description - * (e.g. description of struct members for a record type) - */ - void assign_tdextra(std::unique_ptr tdx); + /* call this once to attach extended type information to a type-description + * (e.g. description of struct members for a record type) + */ + void assign_tdextra(std::unique_ptr tdx); - private: - TypeDescrBase(TypeId id, - std::type_info const * tinfo, - std::string_view canonical_name, - std::unique_ptr tdextra); + private: + TypeDescrBase(TypeId id, + std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra); - private: - /* invariant: - * - for all TypeDescrImpl instances x: - * - s_type_table_v[x->id()] = x - * - s_type_table_map[TypeInfoRef(x->typeinfo())] = x - */ + private: + /* invariant: + * - for all TypeDescrImpl instances x: + * - s_type_table_v[x->id()] = x + * - s_type_table_map[TypeInfoRef(x->typeinfo())] = x + */ - /* hashmap of all TypeDescr instances, indexed by . singleton */ - static std::unordered_map> s_type_table_map; - /* hashmap of (presumed) duplicate TypeInfoRef values. - * This happens with clang sometimes when the same type is referenced - * from multiple modules (i.e. shared libs). - */ - static std::unordered_map s_coalesced_type_table_map; + /* hashmap of all TypeDescr instances, indexed by . singleton */ + static std::unordered_map> s_type_table_map; + /* hashmap of (presumed) duplicate TypeInfoRef values. + * This happens with clang sometimes when the same type is referenced + * from multiple modules (i.e. shared libs). + */ + static std::unordered_map s_coalesced_type_table_map; - /* vector of all TypeDescr instances. singleton. */ - static std::vector s_type_table_v; + /* vector of all TypeDescr instances. singleton. */ + static std::vector s_type_table_v; - private: - /* unique id# for this type */ - TypeId id_; - /* typeinfo for type T */ - std::type_info const * typeinfo_ = nullptr; - /* canonical name for this type (see demangle.hpp for type_name()) - * e.g. - * xo::option::Px2 - */ - std::string_view canonical_name_; - /* suffix of .canonical_name, just after last ':' - * e.g. - * Px2 - */ - std::string_view short_name_; - /* set to true once final value for .tdextra is established - * intially all TypeDescr objects will use AtomicTdx for .tdextra - * Reflect::require() upgrades .tdextra for particular types. - * When that procedure makes a decision for a type T, - * .complete_flag will be set to true for the corresponding TypeDescrBase instance - */ - bool complete_flag_ = false; - /* additional type information that either: - * (a) isn't universal across all types, - * e.g. dereferencing instance of a pointer type - * (b) can't be captured with template-fu, - * e.g. struct member names - * - * generally .tdextra will be populated some time after TypeDescrBase's ctor exits. - * This is necessary because of (b) above, also because of possibility of recursive - * types. - */ - std::unique_ptr tdextra_; - }; /*TypeDescrBase*/ + private: + /* unique id# for this type */ + TypeId id_; + /* typeinfo for type T */ + std::type_info const * typeinfo_ = nullptr; + /* canonical name for this type (see demangle.hpp for type_name()) + * e.g. + * xo::option::Px2 + */ + std::string_view canonical_name_; + /* suffix of .canonical_name, just after last ':' + * e.g. + * Px2 + */ + std::string_view short_name_; + /* set to true once final value for .tdextra is established + * intially all TypeDescr objects will use AtomicTdx for .tdextra + * Reflect::require() upgrades .tdextra for particular types. + * When that procedure makes a decision for a type T, + * .complete_flag will be set to true for the corresponding TypeDescrBase instance + */ + bool complete_flag_ = false; + /* additional type information that either: + * (a) isn't universal across all types, + * e.g. dereferencing instance of a pointer type + * (b) can't be captured with template-fu, + * e.g. struct member names + * + * generally .tdextra will be populated some time after TypeDescrBase's ctor exits. + * This is necessary because of (b) above, also because of possibility of recursive + * types. + */ + std::unique_ptr tdextra_; + }; /*TypeDescrBase*/ - inline std::ostream & - operator<<(std::ostream & os, TypeDescrBase const & x) { - x.display(os); - return os; - } /*operator<<*/ + inline std::ostream & + operator<<(std::ostream & os, TypeDescrBase const & x) { + x.display(os); + return os; + } /*operator<<*/ - } /*namespace reflect*/ + /* tag to drive overload resolution */ + struct reflected_types_printer {}; + + inline std::ostream & + operator<<(std::ostream & os, reflected_types_printer) { + TypeDescrBase::print_reflected_types(os); + return os; + } + + } /*namespace reflect*/ } /*namespace xo*/ /* end TypeDescr.hpp */ From 0707571945155eb9e05e51426d3c96a83eea99c6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:52:44 -0400 Subject: [PATCH 0235/2524] cosmetic: indentation --- src/reflect/TypeDescr.cpp | 300 +++++++++++++++++++------------------- 1 file changed, 150 insertions(+), 150 deletions(-) diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp index 93a0bfc1..b0a0dfd7 100644 --- a/src/reflect/TypeDescr.cpp +++ b/src/reflect/TypeDescr.cpp @@ -7,188 +7,188 @@ #include "xo/indentlog/scope.hpp" namespace xo { - using xo::scope; - using xo::xtag; - using xo::tostr; + using xo::scope; + using xo::xtag; + using xo::tostr; - namespace reflect { - uint32_t - TypeId::s_next_id = 1; + namespace reflect { + uint32_t + TypeId::s_next_id = 1; - std::unordered_map> - TypeDescrBase::s_type_table_map; + std::unordered_map> + TypeDescrBase::s_type_table_map; - std::unordered_map - TypeDescrBase::s_coalesced_type_table_map; + std::unordered_map + TypeDescrBase::s_coalesced_type_table_map; - std::vector - TypeDescrBase::s_type_table_v; + std::vector + TypeDescrBase::s_type_table_v; - TypeDescrW - TypeDescrBase::require(std::type_info const * tinfo, - std::string_view canonical_name, - std::unique_ptr tdextra) - { - /* 1. lookup by tinfo hash_code in s_type_table_map */ - { - auto ix = s_type_table_map.find(TypeInfoRef(tinfo)); + TypeDescrW + TypeDescrBase::require(std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra) + { + /* 1. lookup by tinfo hash_code in s_type_table_map */ + { + auto ix = s_type_table_map.find(TypeInfoRef(tinfo)); - if ((ix != s_type_table_map.end()) && ix->second) - return ix->second.get(); - } + 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)); + /* 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; - } + 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; + /* 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; - } - } + return x; + } + } - TypeId id = TypeId::allocate(); + TypeId id = TypeId::allocate(); - std::unique_ptr & slot = s_type_table_map[TypeInfoRef(tinfo)]; + std::unique_ptr & slot = s_type_table_map[TypeInfoRef(tinfo)]; - slot.reset(new TypeDescrBase(id, - tinfo, - canonical_name, - std::move(tdextra))); + 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); + if (s_type_table_v.size() <= id.id()) + s_type_table_v.resize(id.id() + 1); - s_type_table_v[id.id()] = slot.get(); + s_type_table_v[id.id()] = slot.get(); - return slot.get(); - } /*require*/ + return slot.get(); + } /*require*/ - void - TypeDescrBase::print_reflected_types(std::ostream & os) - { - os << "display(os); - } - } + for (TypeDescrBase * td : s_type_table_v) { + os << "\n "; + if (td) { + td->display(os); + } + } - os << ">\n"; - } /*print_reflected_types*/ + os << ">\n"; + } /*print_reflected_types*/ - namespace { - /* readability hack: - * foo::bar::Quux ==> Quux - * but lookout for template names: - * std::pair ==> pair - */ - std::string_view - unqualified_name(std::string_view const & canonical_name) - { - size_t m = canonical_name.find_first_of('<'); + namespace { + /* readability hack: + * foo::bar::Quux ==> Quux + * but lookout for template names: + * std::pair ==> pair + */ + std::string_view + unqualified_name(std::string_view const & canonical_name) + { + size_t m = canonical_name.find_first_of('<'); - /* skip ':', but only in range [0..m) */ - size_t p = canonical_name.find_last_of(':', m); + /* 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*/ + if (p == std::string_view::npos) { + return canonical_name; + } else { + if ((canonical_name.substr(0, 9) == "std::pair") + || (canonical_name.substr(0, 13) == "std::_1::pair")) + { + return std::string_view("pair"); + } else { + return std::string_view(canonical_name.substr(p+1)); + } + } + } /*unqualified_name*/ + } /*namespace*/ - TypeDescrBase::TypeDescrBase(TypeId id, - std::type_info const * tinfo, - std::string_view canonical_name, - std::unique_ptr tdextra) - : id_{std::move(id)}, - typeinfo_{tinfo}, - canonical_name_{std::move(canonical_name)}, - short_name_{unqualified_name(canonical_name_)}, - tdextra_{std::move(tdextra)} - { - } + TypeDescrBase::TypeDescrBase(TypeId id, + std::type_info const * tinfo, + std::string_view canonical_name, + std::unique_ptr tdextra) + : id_{std::move(id)}, + typeinfo_{tinfo}, + canonical_name_{std::move(canonical_name)}, + short_name_{unqualified_name(canonical_name_)}, + tdextra_{std::move(tdextra)} + { + } - TaggedPtr - TypeDescrBase::most_derived_self_tp(void * object) const - { - return this->tdextra_->most_derived_self_tp(this, object); - } /*most_derived_self_tp*/ + TaggedPtr + TypeDescrBase::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*/ + TaggedPtr + TypeDescrBase::child_tp(uint32_t i, void * object) const + { + return this->tdextra_->child_tp(i, object); + } /*child_tp*/ - void - TypeDescrBase::display(std::ostream & os) const - { - os << "metatype()) - << ">"; - } /*display*/ + void + TypeDescrBase::display(std::ostream & os) const + { + os << "metatype()) + << ">"; + } /*display*/ - std::string - TypeDescrBase::display_string() const - { - return tostr(*this); - } /*display_string*/ + std::string + TypeDescrBase::display_string() const + { + return tostr(*this); + } /*display_string*/ - bool - TypeDescrBase::mark_complete() - { - bool retval = this->complete_flag_; + bool + TypeDescrBase::mark_complete() + { + bool retval = this->complete_flag_; - this->complete_flag_ = true; + this->complete_flag_ = true; - return retval; - } /*mark_complete*/ + return retval; + } /*mark_complete*/ - void - TypeDescrBase::assign_tdextra(std::unique_ptr tdx) - { - scope log(XO_ENTER0(verbose), - xtag("canonical_name", this->canonical_name()), - xtag("tdextra.old", this->tdextra_.get()), - xtag("metatype.old", (this->tdextra_ - ? this->tdextra_->metatype() - : Metatype::mt_invalid)), - xtag("metatype.new", tdx->metatype())); + void + TypeDescrBase::assign_tdextra(std::unique_ptr tdx) + { + scope log(XO_ENTER0(verbose), + xtag("canonical_name", this->canonical_name()), + xtag("tdextra.old", this->tdextra_.get()), + xtag("metatype.old", (this->tdextra_ + ? this->tdextra_->metatype() + : Metatype::mt_invalid)), + xtag("metatype.new", tdx->metatype())); - this->complete_flag_ = true; - this->tdextra_ = std::move(tdx); - } /*assign_tdextra*/ - } /*namespace reflect*/ + this->complete_flag_ = true; + this->tdextra_ = std::move(tdx); + } /*assign_tdextra*/ + } /*namespace reflect*/ } /*namespace xo*/ /* end TypeDescr.cpp */ From 0d6e9afc0a757c0275591341b664632777714582 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:52:53 -0400 Subject: [PATCH 0236/2524] build: tidy in CMakeLists.txt --- utest/CMakeLists.txt | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 8d142b9b..b701f2a6 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -9,22 +9,6 @@ xo_include_options2(${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 -# -#xo_include_options2(${SELF_EXECUTABLE_NAME}) -#target_include_directories(${SELF_EXECUTABLE_NAME} PUBLIC -# ${PROJECT_SOURCE_DIR} -# ${PROJECT_BINARY_DIR}) - # ---------------------------------------------------------------- # internal dependencies: logutil, ... @@ -35,15 +19,6 @@ xo_self_dependency(${SELF_EXECUTABLE_NAME} reflect) xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) -# 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 From a4c264d8db2f02e7d7bc433e22f571b65450aba1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:54:27 -0400 Subject: [PATCH 0237/2524] randomgen: distribution + related concepts --- include/xo/randomgen/distribution_concept.hpp | 35 +++++++++++++ include/xo/randomgen/generator.hpp | 49 +++++++++++++++++++ include/xo/randomgen/normalgen.hpp | 14 ++++++ 3 files changed, 98 insertions(+) create mode 100644 include/xo/randomgen/distribution_concept.hpp create mode 100644 include/xo/randomgen/generator.hpp create mode 100644 include/xo/randomgen/normalgen.hpp diff --git a/include/xo/randomgen/distribution_concept.hpp b/include/xo/randomgen/distribution_concept.hpp new file mode 100644 index 00000000..61c764e0 --- /dev/null +++ b/include/xo/randomgen/distribution_concept.hpp @@ -0,0 +1,35 @@ +/* @file distribution_concept.hpp */ + +#pragma once + +#include + +namespace xo { + namespace rng { + template + concept distribution_concept = requires(Distribution dist, typename Distribution::param_type p) { + typename Distribution::result_type; + typename Distribution::param_type; + { Distribution() }; + { Distribution(p) }; + { dist.reset() }; + { dist.param() }; + { dist.param(p) }; + // { dist(g) }; // generator g satisfying engine_concept + // { dist(g, p) }; + { dist.min() }; + { dist.max() }; + { dist == dist }; + { dist != dist }; + // os << dist + // is >> dist + + } && std::copyable + && std::copyable + && std::equality_comparable; + } /*namespace rng*/ + +} /*namespace xo*/ + + +/* end distribution_concept.hpp */ diff --git a/include/xo/randomgen/generator.hpp b/include/xo/randomgen/generator.hpp new file mode 100644 index 00000000..f35c0994 --- /dev/null +++ b/include/xo/randomgen/generator.hpp @@ -0,0 +1,49 @@ +/* @file generator.hpp */ + +#pragma once + +#include "engine_concept.hpp" +#include "distribution_concept.hpp" +#include + +namespace xo { + namespace rng { + /* Engine: uniform integer random number generator, e.g. xoshiro256ss + * Distribution: random number distribution, e.g. std::normal_distribution + */ + template requires engine_concept && distribution_concept + class generator { + public: + using result_type = typename Distribution::result_type; + using engine_type = Engine; + + public: + generator(Engine & e, Distribution const & d) + : engine_{e}, + distribution_{d} {} + generator(Engine && e, Distribution && d) + : engine_{std::move(e)}, + distribution_{std::move(d)} {} + + static generator make(Engine e, Distribution d) { + return generator(e, d); + } + + result_type operator()() { return this->distribution_(this->engine_); } + + private: + /* random number generator; generates uniformly-distributed integers */ + Engine engine_; + /* distribution object */ + Distribution distribution_; + }; /*generator*/ + + template + generator make_generator(Engine e, Distribution d) { + return generator::make(std::move(e), + std::move(d)); + } /*make_generator*/ + } /*namespace rng*/ +} /*namespace xo*/ + +/* end generator.hpp */ diff --git a/include/xo/randomgen/normalgen.hpp b/include/xo/randomgen/normalgen.hpp new file mode 100644 index 00000000..dad04328 --- /dev/null +++ b/include/xo/randomgen/normalgen.hpp @@ -0,0 +1,14 @@ +/* @file normalgen.hpp */ + +#pragma once + +#include "generator.hpp" +#include + +namespace xo { + namespace rng { + /* Engine: e.g. xo::rng::xoshiro256 or std::mt19937 */ + template + using normalgen = generator>; + } /*namespace rng*/ +} /*namespace xo*/ From 2040d530b8c91d12d78df5e920bf588456714348 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 14:55:25 -0400 Subject: [PATCH 0238/2524] build: streamlining, use xo_install_library3() --- CMakeLists.txt | 9 +++++---- utest/CMakeLists.txt | 26 ++------------------------ 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 94bd08fa..6a7b8377 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,14 +33,15 @@ xo_toplevel_compile_options() add_subdirectory(utest) -set(SELF_LIB xo_ordinaltree) +# ---------------------------------------------------------------- +# header-only library -add_library(${SELF_LIB} INTERFACE) -xo_include_headeronly_options2(${SELF_LIB}) +set(SELF_LIB ordinaltree) +xo_add_headeronly_library(${SELF_LIB}) # ---------------------------------------------------------------- # -xo_install_library2(${PROJECT_NAME}) +xo_install_library3(${SELF_LIB} ${PROJECT_NAME}Targets) xo_install_include_tree() # (note: ..Targets from xo_install_library2()) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 26b370b4..30311ce5 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -1,4 +1,4 @@ -# tree/utest/CMakeLists.txt +# ordinaltree/utest/CMakeLists.txt # note: tests in this directory use Catch2-provided main set(SELF_EXE utest.tree) @@ -22,26 +22,4 @@ xo_dependency(${SELF_EXE} randomgen) xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) -# 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 -# - -# let's see if xo_external_target_dependency() works for these.. -#find_path(CATCH_INCLUDE_DIR "catch2/catch.hpp") -#target_include_directories(${SELF_UTEST_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 tree/utest/CMakeLists.txt +# end ordinaltree/utest/CMakeLists.txt From b8d15cc1fa5aeeceb76c55511db01a7dfe220d30 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:02:57 -0400 Subject: [PATCH 0239/2524] mention dependencies in README --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 41625b26..9adda0d5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # ordinal tree library +## Getting Started + +### build + install dependencies + +- see [github/Rconybea/randomgen](https://github.com/Rconybea/randomgen) -- random number generators e.g. xoshiro256ss +- see [github/Rconybea/refcnt](https://github.com/Rconybea/refcnt) -- intrusive reference-counting + ### build + install ``` $ cd xo-ordinaltree From 4468aa0ee173b482c48b0bac3656cc49ef14ff9c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:06:30 -0400 Subject: [PATCH 0240/2524] doc: README improvements --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d9bbae39..3aa5ee81 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # pybind11 utilities for XO projects -# to build + install locally +## Getting Started + +### build + install dependencies + +- see [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) + +### to build + install locally ``` $ cd xo-pyutil @@ -11,3 +17,9 @@ $ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=$(PREFIX) $ make $ make install ``` + +### LSP support +``` +$ cd xo-pyutil +$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` From 4246f336425a1730cb4bbab244594e94c1670922 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:13:19 -0400 Subject: [PATCH 0241/2524] build: track xo-cmake streamlining --- CMakeLists.txt | 2 +- src/pyreflect/CMakeLists.txt | 4 ++-- src/pyreflect/pyreflect.cpp | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c372fd5..ccf4b129 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,7 @@ add_subdirectory(src/pyreflect) # ---------------------------------------------------------------- # provide find_package() support -xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} pyreflectTargets) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- # install .hpp files diff --git a/src/pyreflect/CMakeLists.txt b/src/pyreflect/CMakeLists.txt index 0579d761..94a71814 100644 --- a/src/pyreflect/CMakeLists.txt +++ b/src/pyreflect/CMakeLists.txt @@ -1,4 +1,4 @@ -# xo_pyreflect/CMakeLists.txt +# xo_pyreflect/src/pyreflect/CMakeLists.txt set(SELF_LIB pyreflect) set(SELF_SRCS pyreflect.cpp) @@ -6,7 +6,7 @@ set(SELF_SRCS pyreflect.cpp) # ---------------------------------------------------------------- # pybind11 dep -xo_pybind11_library(${SELF_LIB} ${SELF_SRCS}) +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} reflect) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index 3a9f81bb..0b638658 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -26,7 +26,8 @@ namespace xo { //py::class_(m, "utc_nanos"); //py::class_(m, "TypeDescr"); - py::class_>(m, "TypeDescr") + py::class_>(m, "TypeDescr") .def_static("print_reflected_types", [](){ TypeDescrBase::print_reflected_types(std::cout); }) .def_property_readonly("canonical_name", &TypeDescrBase::canonical_name) From bc55885778898247204a3b2392ace02de95e24a2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:13:58 -0400 Subject: [PATCH 0242/2524] doc: README additions --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 434a80a5..7be07dd9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # python bindings for c++ reflection library (xo-reflect) -## build + install +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-reflect](https://github.com/Rconybea/xo-reflect) + +### build + install ``` $ cd xo-pyreflect $ mkdir build @@ -13,8 +20,9 @@ $ cmake \ $ make $ make install ``` +(also see .github/workflows/main.yml) -## build for unit test coverage +### build for unit test coverage ``` $ cd xo-pyreflect $ mkdir build-ccov @@ -26,7 +34,7 @@ $ cmake \ -DCMAKE_BUILD_TYPE=Debug .. ``` -## LSP (language server) support +### LSP (language server) support LSP looks for compile commands in the root of the source tree; while Cmake creates them in the root of its build directory. From 465a712252eefdd0af9559193b6cda0ae8b1211b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:18:40 -0400 Subject: [PATCH 0243/2524] build: xo_add_shared_library() -> xo_add_shared_library3() --- cmake/printjsonConfig.cmake.in | 1 + src/printjson/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/printjsonConfig.cmake.in b/cmake/printjsonConfig.cmake.in index e7f7f1be..9417e239 100644 --- a/cmake/printjsonConfig.cmake.in +++ b/cmake/printjsonConfig.cmake.in @@ -2,5 +2,6 @@ include(CMakeFindDependencyMacro) find_dependency(reflect) + include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/src/printjson/CMakeLists.txt b/src/printjson/CMakeLists.txt index cf191d02..b0a4d532 100644 --- a/src/printjson/CMakeLists.txt +++ b/src/printjson/CMakeLists.txt @@ -3,7 +3,7 @@ set(SELF_LIB printjson) set(SELF_SRCS PrintJson.cpp init_printjson.cpp) -xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # dependencies: indentlog, ... From 652362a127e70c06e2c26bdffb9bb1aaea2eacfc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:18:57 -0400 Subject: [PATCH 0244/2524] doc: README additions --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 99492884..28ef1b15 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # printjson library -# build + install +## Getting Started + +### build + install dependencies + +- [github/Rconybea/reflect](https://github.com/Rconybea/reflect) + +### build + install + ``` $ cd xo-printjson $ mkdir build @@ -11,7 +18,8 @@ $ make $ make install ``` -# build for unit test coverage +### build for unit test coverage + ``` $ cd xo-printjson $ mkdir ccov @@ -22,7 +30,8 @@ $ make ccov # runs instrumented unit tests $ make ccov-all # generates lcov report ``` -# LSP support +### LSP support + ``` $ cd xo-printjson $ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree From add10041086a807c9c984f3e0506b2aaaf20584f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:21:24 -0400 Subject: [PATCH 0245/2524] doc: README additions --- CMakeLists.txt | 2 +- README.md | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b0079d4..d67984af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ add_code_coverage() add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* ${PROJECT_BINARY_DIR}/local/* ${PROJECT_SOURCE_DIR}/repo/*) # ---------------------------------------------------------------- -# c++ settings +# common c++ settings # PROJECT_CXX_FLAGS: bespoke for this project - usually empty set(PROJECT_CXX_FLAGS "") diff --git a/README.md b/README.md index fcfa8e8a..53cc5849 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,14 @@ Reentrant: even while being invoked. 2. Any such re-entrant operations are deferred until callback invocation completes. -# build + install +## Getting Started + +### build + install dependencies + +- [github/Rconybea/refcnt](https://github.com/Rconybea/refcnt) + +### build + install + ``` $ cd xo-callback $ mkdir build @@ -20,7 +27,8 @@ $ make install ``` (also see .github/workflows/main.yml) -# build for unit test coverage +### build for unit test coverage + ``` $ cd xo-callback $ mkdir build-ccov @@ -32,7 +40,7 @@ $ cmake \ -DCMAKE_BUILD_TYPE=Debug .. ``` -# LSP (language server) support +### LSP (language server) support LSP looks for compile commands in the root of the source tree; cmake creates them in the root of its build directory. From 86b9a40ae7740f6f7e6aea2917138a405deea1d0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:22:06 -0400 Subject: [PATCH 0246/2524] header-only implementation --- include/xo/callback/CallbackSet.hpp | 27 ++++++++++++++++++++------- src/callback/CallbackSet.cpp | 9 --------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp index c15aeb34..04c83800 100644 --- a/include/xo/callback/CallbackSet.hpp +++ b/include/xo/callback/CallbackSet.hpp @@ -17,23 +17,36 @@ namespace xo { * * can use id to remove callback later: * cbset.remove_callback(cb_id); + * + * Tag so xo-callback can be header-only */ - class CallbackId { + template + class CallbackIdImpl { public: - CallbackId() = default; - explicit CallbackId(uint32_t id) : id_{id} {} + CallbackIdImpl() = default; + explicit CallbackIdImpl(uint32_t id) : id_{id} {} /* generate a globally-unique id (not threadsafe) */ - static CallbackId generate(); + static CallbackIdImpl generate() { + static CallbackIdImpl s_last_id; + + s_last_id = CallbackIdImpl(s_last_id.id() + 1); + + return s_last_id; + } /*generate*/ uint32_t id() const { return id_; } private: uint32_t id_ = 0; - }; /*CallbackId*/ + }; /*CallbackIdImpl*/ - inline bool operator==(CallbackId lhs, CallbackId rhs) { return lhs.id() == rhs.id(); } - inline bool operator!=(CallbackId lhs, CallbackId rhs) { return lhs.id() != rhs.id(); } + template + inline bool operator==(CallbackIdImpl lhs, CallbackIdImpl rhs) { return lhs.id() == rhs.id(); } + template + inline bool operator!=(CallbackIdImpl lhs, CallbackIdImpl rhs) { return lhs.id() != rhs.id(); } + + using CallbackId = CallbackIdImpl; /* queue add/remove callback instructions encountered during callback * execution, to avoid invalidating vector iterator. diff --git a/src/callback/CallbackSet.cpp b/src/callback/CallbackSet.cpp index 881f57f8..1a889f3a 100644 --- a/src/callback/CallbackSet.cpp +++ b/src/callback/CallbackSet.cpp @@ -7,15 +7,6 @@ namespace xo { namespace fn { - CallbackId - CallbackId::generate() - { - static CallbackId s_last_id; - - s_last_id = CallbackId(s_last_id.id() + 1); - - return s_last_id; - } /*generate*/ } /*namespace fn*/ } /*namespace xo*/ From 4b65f3bb9577ca6949face5bfcc1895bda2d51e6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:26:46 -0400 Subject: [PATCH 0247/2524] callbackset: drop Refcounted header --- include/xo/callback/CallbackSet.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp index 04c83800..9bba6514 100644 --- a/include/xo/callback/CallbackSet.hpp +++ b/include/xo/callback/CallbackSet.hpp @@ -2,11 +2,11 @@ #pragma once -#include "xo/refcnt/Refcounted.hpp" //#include "indentlog/scope.hpp" //#include "indentlog/print/tag.hpp" #include #include +#include namespace xo { namespace fn { From 2de57c1ec3643544d40b0781e29eeba3c74e32de Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:30:13 -0400 Subject: [PATCH 0248/2524] doc: README additions --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a8eb8c77..34463fb0 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ # webutil library (header-only) -# dependencies +## Getting Started -- xo-cmake [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) +### build + install dependencies + +- xo-callback [github/Rconybea/xo-callback](https://github.com/Rconybea/xo-callback) + +### clone repo -# clone repo ``` $ git clone git@github.com:Rconybea/xo-webutil.git ``` -# build and install +### build and install + ``` $ cd xo-webutil $ BUILDDIR=build # for example @@ -21,7 +25,8 @@ $ make $ make install ``` -# LSP support +### LSP support + ``` $ cd xo-webutil $ ln -s $BUILDDIR/compile_commands.json From 8e1f89440edcc8fa2853be4de451bb4eb973f77e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:35:13 -0400 Subject: [PATCH 0249/2524] build: make callback header-only --- src/callback/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/callback/CMakeLists.txt b/src/callback/CMakeLists.txt index 58dc3de5..114a89e2 100644 --- a/src/callback/CMakeLists.txt +++ b/src/callback/CMakeLists.txt @@ -1,14 +1,14 @@ # callback/CMakeLists.txt set(SELF_LIB callback) -set(SELF_SRCS CallbackSet.cpp) +#set(SELF_SRCS CallbackSet.cpp) -# reminder: can't be header-only library, because depends on non-header-only refcnt -xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_headeronly_library(${SELF_LIB}) +xo_install_library3(${SELF_LIB} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- # external dependencies: -xo_dependency(${SELF_LIB} refcnt) +#xo_dependency(${SELF_LIB} refcnt) # end CMakeLists.txt From 8d0a327aadc6003508ee9582b34ab15f2f90160e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:36:11 -0400 Subject: [PATCH 0250/2524] .gitignore: + compile_commands.json --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 9e716afc..e9746d99 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# symlink to path/to/build/compile_commands.json should be manual +compile_commands.json # lsp keeps state here .cache # typical build directories From 4215fd98ec8e2239efc8ddf8e7707d4d47a243ef Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:49:33 -0400 Subject: [PATCH 0251/2524] doc: README additions --- README.md | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 02b7e7a2..8ab8e106 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,17 @@ in-memory queuing system -# dependencies +## Getting Started + +### build + install dependencies build+install these first -- xo-reflect [github.com/Rconybea/xo-reflect] -- xo-callback [github.com/Rconybea/xo-callback] +- xo-reflect [github.com/Rconybea/xo-reflect](https://github.com/Rconybea/reflect) +- xo-callback [github.com/Rconybea/xo-callback](https://github.com/Rconybea/xo-callback) +- xo-webutil [github.com/Rconybea/xo-webutil](https://github.com/Rconybea/xo-webutil) -# build + install - -# build +### build + install xo-reactor ``` $ cd reactor $ mkdir build @@ -23,7 +24,7 @@ $ make install ``` (also see .github/workflows/main.yml) -# build for unit test coverage +### build for unit test coverage ``` $ cd xo-reactor $ mkdir ccov @@ -31,7 +32,9 @@ $ cd ccov $ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. ``` -# LSP support +## Development + +### LSP support LSP looks for compile commands in the root of the source tree; cmake creates them in the root of its build directory. @@ -40,3 +43,14 @@ cmake creates them in the root of its build directory. $ cd xo-reactor $ ln -s build/compile_commands.json ``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd reactor/build +$ cmake -LAH +``` From 10cef974742257a5e116e2fe240f339f0cba40ad Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:49:55 -0400 Subject: [PATCH 0252/2524] build: + dependencies --- CMakeLists.txt | 4 ++-- cmake/reactorConfig.cmake.in | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ddd7483..2e0be181 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,12 +40,12 @@ add_subdirectory(src/reactor) add_subdirectory(utest) # ---------------------------------------------------------------- -# provide find_pacakge() support for reactor customers +# provide find_package() support for reactor customers xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- -# install .hpp files +# install project .hpp files xo_install_include_tree() diff --git a/cmake/reactorConfig.cmake.in b/cmake/reactorConfig.cmake.in index 5456d16b..19ea52a2 100644 --- a/cmake/reactorConfig.cmake.in +++ b/cmake/reactorConfig.cmake.in @@ -6,7 +6,10 @@ include(CMakeFindDependencyMacro) # must coordinate with xo_dependency() calls # in xo-reactor/src/reactor/CMakeLists.txt # +find_dependency(xo_ordinaltree) find_dependency(reflect) +find_dependency(webutil) +find_dependency(printjson) find_dependency(callback) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") From f7d2b0494178d90d9ad0d238eb1f39fbe9812272 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:50:23 -0400 Subject: [PATCH 0253/2524] build: xo_ordinaltree / printjson deps --- src/reactor/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/reactor/CMakeLists.txt b/src/reactor/CMakeLists.txt index 6e26ee50..884e466c 100644 --- a/src/reactor/CMakeLists.txt +++ b/src/reactor/CMakeLists.txt @@ -16,6 +16,8 @@ xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) # must coordinate with find_dependency() calls # in xo-reactor/cmake/reactorConfig.cmake.in # +#xo_dependency(${SELF_LIB} xo_ordinaltree) # for some reason wants to link -lxo_ordinaltree ?? xo_dependency(${SELF_LIB} reflect) xo_dependency(${SELF_LIB} webutil) +xo_dependency(${SELF_LIB} printjson) xo_dependency(${SELF_LIB} callback) From 7e9eb6b6df75968b1ad5ac1b7383c1c3f8b765d4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:50:47 -0400 Subject: [PATCH 0254/2524] bugfix: logging verbosity --- src/reactor/PollingReactor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactor/PollingReactor.cpp b/src/reactor/PollingReactor.cpp index a9c52cb7..c8c9fe66 100644 --- a/src/reactor/PollingReactor.cpp +++ b/src/reactor/PollingReactor.cpp @@ -79,7 +79,7 @@ namespace xo { { int64_t ix = this->find_nonempty_source(this->next_ix_); - scope log(XO_DEBUG(this->loglevel() == log_level::chatty)); + scope log(XO_DEBUG(this->loglevel() <= log_level::chatty)); log && log(xtag("self", this), xtag("src_ix", ix)); From c07d1c23e17a0e2750c8631a5d7cc7179524deca Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 15:51:02 -0400 Subject: [PATCH 0255/2524] compile fixes + indentation --- include/xo/reactor/EventStore.hpp | 500 +++++++++++++++--------------- include/xo/reactor/Reducer.hpp | 42 +-- 2 files changed, 271 insertions(+), 271 deletions(-) diff --git a/include/xo/reactor/EventStore.hpp b/include/xo/reactor/EventStore.hpp index 62ab7f9a..4a5479b2 100644 --- a/include/xo/reactor/EventStore.hpp +++ b/include/xo/reactor/EventStore.hpp @@ -2,316 +2,316 @@ #pragma once -#include "reactor/Reducer.hpp" -#include "reactor/EventTimeFn.hpp" -#include "reactor/Sink.hpp" -#include "web_util/HttpEndpointDescr.hpp" -#include "printjson/PrintJson.hpp" -#include "reflect/Reflect.hpp" -#include "tree/RedBlackTree.hpp" +#include "Reducer.hpp" +#include "EventTimeFn.hpp" +#include "Sink.hpp" +#include "xo/webutil/HttpEndpointDescr.hpp" +#include "xo/printjson/PrintJson.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/ordinaltree/RedBlackTree.hpp" namespace xo { - namespace reactor { + namespace reactor { - /* abstract event store api */ - class AbstractEventStore : virtual public ref::Refcount { - public: - using PrintJson = xo::json::PrintJson; - using TaggedPtr = xo::reflect::TaggedPtr; - using HttpEndpointDescr = xo::web::HttpEndpointDescr; - using Alist = xo::web::Alist; + /* abstract event store api */ + class AbstractEventStore : virtual public ref::Refcount { + public: + using PrintJson = xo::json::PrintJson; + using TaggedPtr = xo::reflect::TaggedPtr; + using HttpEndpointDescr = xo::web::HttpEndpointDescr; + using Alist = xo::web::Alist; - public: - /* true iff .size() == 0 */ - virtual bool empty() const = 0; + public: + /* true iff .size() == 0 */ + virtual bool empty() const = 0; - /* #of events currently held in this store */ - virtual std::uint32_t size() const = 0; + /* #of events currently held in this store */ + virtual std::uint32_t size() const = 0; - /* TODO: - * 1. TaggedGptr = discriminated union of - * a. TaggedRcptr (i.e. refcounted semantics) - * b. Unique (i.e. unique_ptr semantics) - * c. Exptr (i.e. unowned_ptr semantics) - * d. compact (special-case -- value fits in pointer) - * will need mpl copy/assign stuff for TaggedUnique - * 2. provide .last_n(), .last_dt() - */ + /* TODO: + * 1. TaggedGptr = discriminated union of + * a. TaggedRcptr (i.e. refcounted semantics) + * b. Unique (i.e. unique_ptr semantics) + * c. Exptr (i.e. unowned_ptr semantics) + * d. compact (special-case -- value fits in pointer) + * will need mpl copy/assign stuff for TaggedUnique + * 2. provide .last_n(), .last_dt() + */ - virtual void http_snapshot(ref::rp const & pjson, - std::ostream * p_os) const = 0; + virtual void http_snapshot(ref::rp const & pjson, + std::ostream * p_os) const = 0; - /* http endpoint; generates http output for this eventstore */ - virtual HttpEndpointDescr http_endpoint_descr(ref::rp const & pjson, - std::string const & url_prefix) const { + /* http endpoint; generates http output for this eventstore */ + virtual HttpEndpointDescr http_endpoint_descr(ref::rp const & pjson, + std::string const & url_prefix) const { - /* important that lambda contains its own rp; - * reference to stack will not do - */ - ref::rp pjson_rp = pjson; + /* important that lambda contains its own rp; + * reference to stack will not do + */ + ref::rp pjson_rp = pjson; - auto http_fn = ([this, pjson_rp] - (std::string const & /*uri*/, - Alist const & /*alist*/, - std::ostream * p_os) - { - /* WARNING: race condition here, - * given webserver runs from a separate thread - */ + auto http_fn = ([this, pjson_rp] + (std::string const & /*uri*/, + Alist const & /*alist*/, + std::ostream * p_os) + { + /* WARNING: race condition here, + * given webserver runs from a separate thread + */ - this->http_snapshot(pjson_rp, p_os); - }); + this->http_snapshot(pjson_rp, p_os); + }); - return HttpEndpointDescr(url_prefix + "/snap", http_fn); - } /*http_endpoint_descr*/ + return HttpEndpointDescr(url_prefix + "/snap", http_fn); + } /*http_endpoint_descr*/ - virtual void clear() = 0; + virtual void clear() = 0; - virtual void insert_tp(TaggedPtr const & ev_tp) = 0; - }; /*AbstractEventStore*/ + virtual void insert_tp(TaggedPtr const & ev_tp) = 0; + }; /*AbstractEventStore*/ - /* in-memory storage for a set of events. - * - * Require: - * - Event is null-constructible - * - Event is copyable - * - EventTimeFn :: Event -> utc_nanos - * - * inheritance - * ref::Refcount - * ^ - * isa - * | - * reactor::AbstractEventProcessor + req .visit_direct_consumers() - * ^ - * isa - * | - * reactor::AbstractSink + req .sink_ev_type(), .notify_ev() etc. - * ^ - * isa - * | - * reactor::Sink1 + .attach_source(), .sink_ev_type(), - * ^ req .notify_ev() etc - * | - * isa - * | - * reactor::SinkEndpoint + impl .visit_direct_consumers() - * ^ - * isa - * | - * reactor::StructEventStore + .last_n() .last_dt() etc. - */ - template - class EventStoreImpl : public SinkEndpoint, - public AbstractEventStore, - ReducerBase - { - static_assert(EventTimeConcept); + /* in-memory storage for a set of events. + * + * Require: + * - Event is null-constructible + * - Event is copyable + * - EventTimeFn :: Event -> utc_nanos + * + * inheritance + * ref::Refcount + * ^ + * isa + * | + * reactor::AbstractEventProcessor + req .visit_direct_consumers() + * ^ + * isa + * | + * reactor::AbstractSink + req .sink_ev_type(), .notify_ev() etc. + * ^ + * isa + * | + * reactor::Sink1 + .attach_source(), .sink_ev_type(), + * ^ req .notify_ev() etc + * | + * isa + * | + * reactor::SinkEndpoint + impl .visit_direct_consumers() + * ^ + * isa + * | + * reactor::StructEventStore + .last_n() .last_dt() etc. + */ + template + class EventStoreImpl : public SinkEndpoint, + public AbstractEventStore, + ReducerBase + { + static_assert(EventTimeConcept); - public: - using utc_nanos = xo::time::utc_nanos; - using nanos = xo::time::nanos; - using EventTree = xo::tree::RedBlackTree>; - using PrintJson = xo::json::PrintJson; - using Alist = xo::web::Alist; - using HttpEndpointDescr = xo::web::HttpEndpointDescr; + public: + using utc_nanos = xo::time::utc_nanos; + using nanos = xo::time::nanos; + using EventTree = xo::tree::RedBlackTree>; + using PrintJson = xo::json::PrintJson; + using Alist = xo::web::Alist; + using HttpEndpointDescr = xo::web::HttpEndpointDescr; - static ref::rp make() { return new EventStoreImpl(); } + static ref::rp make() { return new EventStoreImpl(); } - /* visit most recent n events in this store. - * returns #of events actually visited - * - * if events visited are e1 .. en, then: - * (1) en is the most recent recorded event - * (.event_tm(en) is .tree.max_key()) - * (2) there are no events between e(i) and e(i+1) - * (i.e. visit does not skip over any events) - * (3) if v < n, then v = .size(), - * where v is the #of events visited - * - * require: - * - Fn :: (Event -> ) - */ - template - std::uint32_t visit_last_n(std::uint32_t n, Fn && fn) const { - std::uint32_t z = this->size(); - std::uint32_t lo = ((n >= z) ? 0 : z - n); + /* visit most recent n events in this store. + * returns #of events actually visited + * + * if events visited are e1 .. en, then: + * (1) en is the most recent recorded event + * (.event_tm(en) is .tree.max_key()) + * (2) there are no events between e(i) and e(i+1) + * (i.e. visit does not skip over any events) + * (3) if v < n, then v = .size(), + * where v is the #of events visited + * + * require: + * - Fn :: (Event -> ) + */ + template + std::uint32_t visit_last_n(std::uint32_t n, Fn && fn) const { + std::uint32_t z = this->size(); + std::uint32_t lo = ((n >= z) ? 0 : z - n); - typename EventTree::const_iterator lo_ix = this->tree_.find_ith(lo); - typename EventTree::const_iterator hi_ix = this->tree_.cend(); + typename EventTree::const_iterator lo_ix = this->tree_.find_ith(lo); + typename EventTree::const_iterator hi_ix = this->tree_.cend(); - return this->visit_range(lo_ix, hi_ix, fn); - } /*visit_last_n*/ + return this->visit_range(lo_ix, hi_ix, fn); + } /*visit_last_n*/ - /* visit suffix of events sufficient to cover interval of length dt. - * visit events in increasing timestamp order. - * - * if events visited are e1 .. en, then: - * (1) en is the most recent recorded event - * (.event_tm(en) is .tree.max_key()) - * (2) there are no events between e(i) and e(i+1) - * (i.e. visit does not skip over any events) - * (3) if .event_tm(en) - .event_tm(e1) < dt, - * then e1 is the earliest recorded event - * (.event_tm(e1) is .tree.min_key()) - * (4) if .event_tm(en) - .event_tm(e1) > dt, - * then (.event_tm(en) - .event_tm(e2)) < dt - * - * |<---------- dt ----------->| - * ^ ^ ^ - * e1 e2 en - */ - template - std::uint32_t visit_last_dt(nanos dt, Fn && fn) const { - if (tree_.empty()) - return 0; + /* visit suffix of events sufficient to cover interval of length dt. + * visit events in increasing timestamp order. + * + * if events visited are e1 .. en, then: + * (1) en is the most recent recorded event + * (.event_tm(en) is .tree.max_key()) + * (2) there are no events between e(i) and e(i+1) + * (i.e. visit does not skip over any events) + * (3) if .event_tm(en) - .event_tm(e1) < dt, + * then e1 is the earliest recorded event + * (.event_tm(e1) is .tree.min_key()) + * (4) if .event_tm(en) - .event_tm(e1) > dt, + * then (.event_tm(en) - .event_tm(e2)) < dt + * + * |<---------- dt ----------->| + * ^ ^ ^ + * e1 e2 en + */ + template + std::uint32_t visit_last_dt(nanos dt, Fn && fn) const { + if (tree_.empty()) + return 0; - /* tree not empty -> has max key */ - utc_nanos tn = this->tree_.max_key(); - utc_nanos tk = tn - dt; + /* tree not empty -> has max key */ + utc_nanos tn = this->tree_.max_key(); + utc_nanos tk = tn - dt; - typename EventTree::const_iterator lo_ix = this->tree_.find_glb(tk, true /*closed*/); - typename EventTree::const_iterator hi_ix = this->tree_.end(); + typename EventTree::const_iterator lo_ix = this->tree_.find_glb(tk, true /*closed*/); + typename EventTree::const_iterator hi_ix = this->tree_.end(); - return this->visit_range(lo_ix, hi_ix, fn); - } /*visit_last_dt*/ + return this->visit_range(lo_ix, hi_ix, fn); + } /*visit_last_dt*/ - std::vector last_n(std::uint32_t n) const { - std::vector retval; + std::vector last_n(std::uint32_t n) const { + std::vector retval; - auto fn = [&retval](Event const &ev) { retval.push_back(ev); }; + auto fn = [&retval](Event const &ev) { retval.push_back(ev); }; - this->visit_last_n(n, fn); + this->visit_last_n(n, fn); - return retval; - } /*last_n*/ + return retval; + } /*last_n*/ - std::vector last_dt(nanos dt) const { - std::vector retval; + std::vector last_dt(nanos dt) const { + std::vector retval; - auto fn = [&retval](Event const &ev) { retval.push_back(ev); }; + auto fn = [&retval](Event const &ev) { retval.push_back(ev); }; - this->visit_last_dt(dt, fn); + this->visit_last_dt(dt, fn); - return retval; - } /*last_dt*/ + return retval; + } /*last_dt*/ - void insert(Event const & ev) { this->tree_.insert(typename EventTree::value_type(this->event_tm(ev), ev)); } + void insert(Event const & ev) { this->tree_.insert(typename EventTree::value_type(this->event_tm(ev), ev)); } - // ----- Inherited from AbstractEventStore ----- + // ----- Inherited from AbstractEventStore ----- - virtual bool empty() const override { return tree_.empty(); } - virtual std::uint32_t size() const override { return tree_.size(); } + virtual bool empty() const override { return tree_.empty(); } + virtual std::uint32_t size() const override { return tree_.size(); } - /* write http snapshot of current state to *p_os */ - virtual void http_snapshot(ref::rp const & pjson, std::ostream * p_os) const override { - using xo::reflect::Reflect; + /* write http snapshot of current state to *p_os */ + virtual void http_snapshot(ref::rp const & pjson, std::ostream * p_os) const override { + using xo::reflect::Reflect; - /* visit last 100 events; - * write them to *p_os in increasing time order - */ - auto ev_v = this->last_n(100); + /* visit last 100 events; + * write them to *p_os in increasing time order + */ + auto ev_v = this->last_n(100); - pjson->print_tp(Reflect::make_tp(&ev_v), p_os); - } /*http_snapshot*/ + pjson->print_tp(Reflect::make_tp(&ev_v), p_os); + } /*http_snapshot*/ - virtual void clear() override { this->tree_.clear(); } + virtual void clear() override { this->tree_.clear(); } - virtual void insert_tp(TaggedPtr const & ev_tp) override { - using xo::xtag; + virtual void insert_tp(TaggedPtr const & ev_tp) override { + using xo::xtag; - Event * p_ev = ev_tp.recover_native(); + Event * p_ev = ev_tp.recover_native(); - if (p_ev) { - this->insert(*p_ev); - } else { - throw std::runtime_error(tostr("StructEventStore::insert_tp" - ": unable to convert ev_tp to Event", - xtag("ev_tp.type", ev_tp.td()->canonical_name()), - xtag("Event", reflect::type_name()))); - } - } /*insert_tp*/ + if (p_ev) { + this->insert(*p_ev); + } else { + throw std::runtime_error(tostr("StructEventStore::insert_tp" + ": unable to convert ev_tp to Event", + xtag("ev_tp.type", ev_tp.td()->canonical_name()), + xtag("Event", reflect::type_name()))); + } + } /*insert_tp*/ - // ----- Inherited from AbstractSink ----- + // ----- Inherited from AbstractSink ----- - virtual uint32_t n_in_ev() const override { return n_in_ev_; } - virtual bool allow_volatile_source() const override { return false; } - virtual void notify_ev(Event const & ev) override { - ++(this->n_in_ev_); - this->insert(ev); - } + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + virtual bool allow_volatile_source() const override { return false; } + virtual void notify_ev(Event const & ev) override { + ++(this->n_in_ev_); + this->insert(ev); + } - // ----- Inherited from AbstractSource ----- + // ----- Inherited from AbstractSource ----- - virtual void display(std::ostream & os) const override { - using xo::xtag; + virtual void display(std::ostream & os) const override { + using xo::xtag; - os << "name()) - << xtag("n_in_ev", this->n_in_ev()) - << ">"; - } /*display*/ + os << "name()) + << xtag("n_in_ev", this->n_in_ev()) + << ">"; + } /*display*/ - // ----- Inherited from AbstractEventProcessor ----- + // ----- Inherited from AbstractEventProcessor ----- - virtual std::string const & name() const override { return name_; } - virtual void set_name(std::string const & x) override { name_ = x; } + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { name_ = x; } - private: - EventStoreImpl() = default; + private: + EventStoreImpl() = default; - template - std::uint32_t visit_range(typename EventTree::const_iterator lo_ix, - typename EventTree::const_iterator hi_ix, - Fn && fn) const { - std::uint32_t n = 0; - for (; lo_ix != hi_ix; ++lo_ix, ++n) { - fn(lo_ix->second); - } + template + std::uint32_t visit_range(typename EventTree::const_iterator lo_ix, + typename EventTree::const_iterator hi_ix, + Fn && fn) const { + std::uint32_t n = 0; + for (; lo_ix != hi_ix; ++lo_ix, ++n) { + fn(lo_ix->second); + } - return n; - } /*visit_range*/ + return n; + } /*visit_range*/ - private: - /* reporting name for this store */ - std::string name_; - /* fetches per-event timestamp */ - EventTimeFn event_tm_fn_; - /* counts lifetime #of incoming events (see .notify_ev()) */ - uint32_t n_in_ev_ = 0; - /* events stored here */ - EventTree tree_; - }; /*EventStoreImpl*/ + private: + /* reporting name for this store */ + std::string name_; + /* fetches per-event timestamp */ + EventTimeFn event_tm_fn_; + /* counts lifetime #of incoming events (see .notify_ev()) */ + uint32_t n_in_ev_ = 0; + /* events stored here */ + EventTree tree_; + }; /*EventStoreImpl*/ - template - using StructEventStore = EventStoreImpl>; + template + using StructEventStore = EventStoreImpl>; - template - using PtrEventStore = EventStoreImpl>; + template + using PtrEventStore = EventStoreImpl>; - /* Require: - * EventTimeConcept> - */ - template - class SinkToEventStore : public SinkEndpoint { - public: - using EventStore = StructEventStore; + /* Require: + * EventTimeConcept> + */ + template + class SinkToEventStore : public SinkEndpoint { + public: + using EventStore = StructEventStore; - public: - SinkToEventStore() = default; + public: + SinkToEventStore() = default; - virtual void notify_ev(T const & ev) override { - store_.insert(ev); - } /*notify_ev*/ + virtual void notify_ev(T const & ev) override { + store_.insert(ev); + } /*notify_ev*/ - private: - /* stash remembered events (all of them!) here */ - EventStore store_; - }; /*SinkToEventStore*/ + private: + /* stash remembered events (all of them!) here */ + EventStore store_; + }; /*SinkToEventStore*/ - } /*namespace reactor*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end EventStore.hpp */ diff --git a/include/xo/reactor/Reducer.hpp b/include/xo/reactor/Reducer.hpp index 907d9d04..a457dfe0 100644 --- a/include/xo/reactor/Reducer.hpp +++ b/include/xo/reactor/Reducer.hpp @@ -2,32 +2,32 @@ #pragma once -#include "reactor/EventTimeFn.hpp" +#include "xo/reactor/EventTimeFn.hpp" namespace xo { - namespace reactor { - /* LastReducer, HeapReducer inherit ReducerBase */ - template - class ReducerBase { - static_assert(EventTimeConcept); + namespace reactor { + /* LastReducer, HeapReducer inherit ReducerBase */ + template + class ReducerBase { + static_assert(EventTimeConcept); - public: - using utc_nanos = xo::time::utc_nanos; + public: + using utc_nanos = xo::time::utc_nanos; - public: - ReducerBase() = default; - ReducerBase(EventTimeFn const & evtfn) : event_tm_fn_{evtfn} {} + public: + ReducerBase() = default; + ReducerBase(EventTimeFn const & evtfn) : event_tm_fn_{evtfn} {} - utc_nanos event_tm(Event const & ev) const { return this->event_tm_fn_(ev); } - - private: - /* Event ev = ...; - * .event_tm_fn(ev) -> utc_nanos - * reports event time associated with ev - */ - EventTimeFn event_tm_fn_; - }; /*ReducerBase*/ - } /*namespace reactor*/ + utc_nanos event_tm(Event const & ev) const { return this->event_tm_fn_(ev); } + + private: + /* Event ev = ...; + * .event_tm_fn(ev) -> utc_nanos + * reports event time associated with ev + */ + EventTimeFn event_tm_fn_; + }; /*ReducerBase*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end Reducer.hpp */ From 3913e07f81a504ea5d3c8ba1844368d2dd10e99e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:06:45 -0400 Subject: [PATCH 0256/2524] first commit --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..314f7fab --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# websock library + +http library including websocket support. +Built around the C-library libwebsocket + +# dependencies + +build+install these first + +- xo-somelib [github.com/Rconybea/xo-somelib] + +# build + install + +## build +``` +$ cd websock +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +## build for unit test coverage +``` +$ cd xo-websock +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +# development + +## LSP support + +LSP looks for compile commands in the root of the source tree; +cmake creates them in the root of its build directory. + +``` +$ cd xo-websock +$ ln -s build/compile_commands.json +``` + +## display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd websock/build +$ cmake -LAH +``` From c634f33e67f44ecb0e0b710d6e0669f5f3746eee Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:07:33 -0400 Subject: [PATCH 0257/2524] implementation + compile as independent module --- CMakeLists.txt | 52 + cmake/websockConfig.cmake.in | 13 + include/xo/websock/DynamicEndpoint.hpp | 126 ++ include/xo/websock/EndpointUtil.hpp | 26 + include/xo/websock/SafetyToken.hpp | 40 + include/xo/websock/Webserver.hpp | 116 + include/xo/websock/WebsockUtil.hpp | 18 + include/xo/websock/WebsocketSink.hpp | 28 + include/xo/websock/WsSafetyToken.hpp | 29 + src/websock/CMakeLists.txt | 16 + src/websock/DynamicEndpoint.cpp | 146 ++ src/websock/EndpointUtil.cpp | 38 + src/websock/Webserver.cpp | 1939 +++++++++++++++++ src/websock/WebsockUtil.cpp | 141 ++ src/websock/WebsocketSink.cpp | 148 ++ utest/CMakeLists.txt | 47 + utest/README | 13 + utest/mount-origin/bluecircle.svg | 7 + utest/mount-origin/d3ex/d3ex.ch5.ex1.html | 15 + utest/mount-origin/ex_websock.html | 13 + utest/mount-origin/ex_websock.js | 842 +++++++ utest/mount-origin/example.js | 64 + utest/mount-origin/index.html | 19 + utest/mount-origin/libwebsockets.org-logo.svg | 66 + utest/mount-origin/script-csp.svg | 53 + utest/websock_utest_main.cpp | 282 +++ 26 files changed, 4297 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/websockConfig.cmake.in create mode 100644 include/xo/websock/DynamicEndpoint.hpp create mode 100644 include/xo/websock/EndpointUtil.hpp create mode 100644 include/xo/websock/SafetyToken.hpp create mode 100644 include/xo/websock/Webserver.hpp create mode 100644 include/xo/websock/WebsockUtil.hpp create mode 100644 include/xo/websock/WebsocketSink.hpp create mode 100644 include/xo/websock/WsSafetyToken.hpp create mode 100644 src/websock/CMakeLists.txt create mode 100644 src/websock/DynamicEndpoint.cpp create mode 100644 src/websock/EndpointUtil.cpp create mode 100644 src/websock/Webserver.cpp create mode 100644 src/websock/WebsockUtil.cpp create mode 100644 src/websock/WebsocketSink.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/README create mode 100644 utest/mount-origin/bluecircle.svg create mode 100644 utest/mount-origin/d3ex/d3ex.ch5.ex1.html create mode 100644 utest/mount-origin/ex_websock.html create mode 100644 utest/mount-origin/ex_websock.js create mode 100644 utest/mount-origin/example.js create mode 100644 utest/mount-origin/index.html create mode 100644 utest/mount-origin/libwebsockets.org-logo.svg create mode 100644 utest/mount-origin/script-csp.svg create mode 100644 utest/websock_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..51006eed --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +# xo-websock/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(websock VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# 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 + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(src/websock) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support for websock customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install .hpp files + +xo_install_include_tree() + +# end CMakeLists.txt diff --git a/cmake/websockConfig.cmake.in b/cmake/websockConfig.cmake.in new file mode 100644 index 00000000..ac43847f --- /dev/null +++ b/cmake/websockConfig.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +#find_dependency(reflect) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/websock/DynamicEndpoint.hpp b/include/xo/websock/DynamicEndpoint.hpp new file mode 100644 index 00000000..8753b835 --- /dev/null +++ b/include/xo/websock/DynamicEndpoint.hpp @@ -0,0 +1,126 @@ +/* file DynamicEndpoint.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "EndpointUtil.hpp" +#include "xo/webutil/HttpEndpointDescr.hpp" +#include "xo/webutil/StreamEndpointDescr.hpp" +#include "xo/webutil/Alist.hpp" +#include + +namespace xo { + namespace web { + /* a dynamic http endpoint. content served on-browser-demand + * by user-provided callback + */ + class DynamicEndpoint { + public: + using AbstractSink = xo::reactor::AbstractSink; + using CallbackId = fn::CallbackId; + + public: + static std::unique_ptr make_http(std::string uri_pattern, + HttpEndpointFn http_cb) { + return (std::unique_ptr + (new DynamicEndpoint(std::move(uri_pattern), + std::move(http_cb), + nullptr, + nullptr))); + } /*make_http*/ + + static std::unique_ptr make_stream(std::string uri_pattern, + StreamSubscribeFn sub_fn, + StreamUnsubscribeFn unsub_fn) { + return (std::unique_ptr + (new DynamicEndpoint(std::move(uri_pattern), + nullptr, + std::move(sub_fn), + std::move(unsub_fn)))); + } /*make_stream*/ + + std::string stem() const { + return EndpointUtil::stem(this->uri_pattern_); + } /*stem*/ + +#ifdef NOT_USING + /* true iff incoming_uri matches .uri_pattern */ + bool is_match(std::string const & incoming_uri) const { + /* c++ regex = javascript regexes, + * so these characters are special: + * ^ $ \ . * + ? ( ) [ ] { } | + */ + } /*is_match*/ +#endif + + /* get html from this endpoint, on behalf of uri=incoming_uri; + * write html on *p_os + * + * require: non-null http_fn + */ + void http_response(std::string const & incoming_uri, + std::ostream * p_os) const; + + /* subscribe stream from this endpoint, on behalf of uri=incoming_uri. + * send output to ws_sink + */ + CallbackId subscribe(std::string const & incoming_uri, + ref::rp const & ws_sink) const; + + /* unsubscribe stream from this endpoint; + * reverses the effect of a previous call to .subscribe() + * that returned id + */ + void unsubscribe(CallbackId id) const; + + private: + explicit DynamicEndpoint(std::string uri_pattern, + HttpEndpointFn http_fn, + StreamSubscribeFn subscribe_fn, + StreamUnsubscribeFn unsubscribe_fn); + + private: + /* pattern for this endpoint + * can be string like + * /fixed/stem/${a}/more/fixed/stuff/${b} + * in which case: + * + * 1. will match uris like: + * /fixed/stem/apple/more/fixed/stuff/bananas + * --> invoke callback with Alist + * ("a" -> "apple", "b" -> "bananas") + * endpoint will be stored in WebserverImpl.stem_map + * under fixed prefix, in this case + * /fixed/stem/ + * + * 2. will not match uris like: + * /fixed/stem/app/le/more/fixed/stuff/bononos + */ + std::string uri_pattern_; + /* regex for matching input that satisfies .uri_pattern: + * each occurrence of + * ${...} replaced by [[:alnum:]]+ + */ + std::regex uri_regex_; + /* variables found in .uri_pattern, + * in the order in which they appear + * if .uri_pattern is + * /fixed/stem/${a}/more/fixed/stuff/${b} + * then .var_v will be: + * ["a", "b"] + */ + std::vector var_v_; + /* run this function to produce an http response */ + HttpEndpointFn http_fn_; + /* run this function to subscribe event stream */ + StreamSubscribeFn subscribe_fn_; + /* run this function to unsubscribe event stream */ + StreamUnsubscribeFn unsubscribe_fn_; + }; /*DynamicEndpoint*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end DynamicEndpoint.hpp */ diff --git a/include/xo/websock/EndpointUtil.hpp b/include/xo/websock/EndpointUtil.hpp new file mode 100644 index 00000000..e0257cd1 --- /dev/null +++ b/include/xo/websock/EndpointUtil.hpp @@ -0,0 +1,26 @@ +/* file EndpointUtil.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include + +namespace xo { + namespace web { + class EndpointUtil { + public: + /* find fixed prefix for a URI pattern. + * patterns are used with both http endpoints (see DynamicEndpoint), + * and stream endpoints (see StreamEndpoint) + * + * e.g. stem("/dyn/uls/${ulticker}/snap") => "/dyn/uls/" + */ + static std::string stem(std::string const & pattern); + }; /*EndpointUtil*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end EndpointUtil.hpp */ diff --git a/include/xo/websock/SafetyToken.hpp b/include/xo/websock/SafetyToken.hpp new file mode 100644 index 00000000..f156b98a --- /dev/null +++ b/include/xo/websock/SafetyToken.hpp @@ -0,0 +1,40 @@ +/* file SafetyToken.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +namespace xo { + namespace web { + /* token for cooperative compile-time threadsafety checking. + * + * requirements for cooperating code: + * - token contains no state, so in principle can be optimized away + * - token is deliberately not copyable, and not moveable + * - derive from token, and make derived ctor private + * - make method/class responsible for threadsafety a friend of token, + * so it can have exclusive right to create a token instance. + * - pass token reference down stack + * to demonstrate ownership of protected resource, + * limited to the lifetime of called function. + */ + template + class SafetyToken { + public: + SafetyToken(SafetyToken const & x) = delete; + SafetyToken(SafetyToken && x) = delete; + + /* optionally: invoke this to "announce use of a protected resource" */ + bool verify() const { return true; } + + SafetyToken & operator=(SafetyToken const & x) = delete; + SafetyToken & operator=(SafetyToken && x) = delete; + + protected: + SafetyToken() = default; + }; /*SafetyToken*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end SafetyToken.hpp */ diff --git a/include/xo/websock/Webserver.hpp b/include/xo/websock/Webserver.hpp new file mode 100644 index 00000000..57b0ccf7 --- /dev/null +++ b/include/xo/websock/Webserver.hpp @@ -0,0 +1,116 @@ +/* @file Webserver.hpp */ + +#pragma once + +#include "xo/refcnt/Displayable.hpp" +#include "xo/printjson/PrintJson.hpp" +#include "xo/webutil/HttpEndpointDescr.hpp" +#include "xo/webutil/StreamEndpointDescr.hpp" +#include // temporary, while moving callbacks +#include +#include +#include + +namespace xo { + namespace web { + enum class Runstate { stopped, stop_requested, running }; + + class RunstateUtil { + public: + static char const * runstate_descr(Runstate x); + }; /*RunstateUtil*/ + + inline std::ostream & operator<<(std::ostream &os, Runstate x) { + os << RunstateUtil::runstate_descr(x); + return os; + } /*operator<<*/ + + class WebserverConfig { + public: + WebserverConfig() = default; + WebserverConfig(std::int32_t port, + bool tls_flag, + bool host_check_flag, + bool use_retry_flag) + : port_{port}, + tls_flag_{tls_flag}, + host_check_flag_{host_check_flag}, + use_retry_flag_{use_retry_flag} {} + + std::int32_t port() const { return port_; } + bool tls_flag() const { return tls_flag_; } + bool host_check_flag() const { return host_check_flag_; } + bool use_retry_flag() const { return use_retry_flag_; } + + private: + /* accept incoming http requests on this port# */ + std::int32_t port_ = 0; + /* if true, support https */ + bool tls_flag_ = false; + /* see LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK */ + bool host_check_flag_ = false; + /* see lws_context_creation_info.retry_and_idle_policy */ + bool use_retry_flag_ = false; + }; /*WebserverConfig*/ + + /* libwebsocket: + * 1. doesn't support multiple threads + * (actually, looks like it does on further examination) + * 2. doesn't expose listening ports etc (at least afaik); + * in other words it expects to take over application's main thread + * + * enforce this property by making webserver a singleton + * + * .state .start_webserver() .state + * +---------+ -------------------> +---------+ + * | stopped | | running | + * +---------+ +---------+ + * ^ | + * | | .stop_webserver() + * | | + * +----------------+ | + * | stop_requested | <------------------/ + * +----------------+ + * + */ + class Webserver : public ref::Displayable { + public: + using Alist = xo::web::Alist; + using PrintJson = xo::json::PrintJson; + + public: + /* note: although webserver allows creating multiple instances, + * the underlying libwebsocket library is not advertised to be + * threadsafe + */ + static ref::rp make(WebserverConfig const & ws_config, + ref::rp const & pjson); + + /* current state */ + virtual Runstate state() const = 0; + virtual void register_http_endpoint(HttpEndpointDescr const & endpoint) = 0; + virtual void register_stream_endpoint(StreamEndpointDescr const & endpoint) = 0; + + /* start thread for this webserver; idempotent */ + virtual void start_webserver() = 0; + /* stop thread for this webserver; suitable for calling + * from interrupt handler + */ + virtual void interrupt_stop_webserver() = 0; + /* stop thread for this webserver; idempotent */ + virtual void stop_webserver() = 0; + /* wait until webserver thread stopped */ + virtual void join_webserver() = 0; + + /* send text to a websocket session identified by session_id */ + virtual void send_text(uint32_t session_id, + std::string text) = 0; + + // ----- Inherited from Displayable ----- + + virtual void display(std::ostream & os) const; + }; /*Webserver*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end Webserver.hpp */ diff --git a/include/xo/websock/WebsockUtil.hpp b/include/xo/websock/WebsockUtil.hpp new file mode 100644 index 00000000..f8626c68 --- /dev/null +++ b/include/xo/websock/WebsockUtil.hpp @@ -0,0 +1,18 @@ +/* @file WebsockUtil.hpp */ + +#pragma once + +#include + +namespace xo { + namespace web { + /* class-as-namespace idiom */ + class WebsockUtil { + public: + /* string representation for callback category enum */ + static char const * ws_callback_reason_descr(lws_callback_reasons x); + }; /*WebsockUtil*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end WebsockUtil.hpp */ diff --git a/include/xo/websock/WebsocketSink.hpp b/include/xo/websock/WebsocketSink.hpp new file mode 100644 index 00000000..863bda34 --- /dev/null +++ b/include/xo/websock/WebsocketSink.hpp @@ -0,0 +1,28 @@ +/* file WebsocketSink.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "xo/reactor/AbstractSink.hpp" +#include "xo/printjson/PrintJson.hpp" + +namespace xo { + namespace web { + class Webserver; + + class WebsocketSink : public reactor::AbstractSink { + public: + using PrintJson = xo::json::PrintJson; + + public: + static ref::rp make(ref::rp const & websrv, + ref::rp const & pjson, + uint32_t session_id, + std::string const & stream_name); + }; /*WebsocketSink*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end WebsocketSink.hpp */ diff --git a/include/xo/websock/WsSafetyToken.hpp b/include/xo/websock/WsSafetyToken.hpp new file mode 100644 index 00000000..0d5302aa --- /dev/null +++ b/include/xo/websock/WsSafetyToken.hpp @@ -0,0 +1,29 @@ +/* file WsSafetyToken.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "SafetyToken.hpp" +#include + +namespace xo { + namespace web { + class WebserverImplWsThread; + class WebsocketSessionRecd; + + /* only websocket thread can obtain this token */ + class WsSafetyToken : public SafetyToken { + private: + friend class WebserverImplWsThread; + + private: + /* only WebserverImpl should construct this */ + WsSafetyToken() = default; + }; /*WsSafetyToken*/ + + } /*namespace web*/ +} /*namespace xo*/ + +/* end WsSafetyToken.hpp */ diff --git a/src/websock/CMakeLists.txt b/src/websock/CMakeLists.txt new file mode 100644 index 00000000..e8aa7df2 --- /dev/null +++ b/src/websock/CMakeLists.txt @@ -0,0 +1,16 @@ +# xo-websock/CMakeLists.txt + +set(SELF_LIB websock) +set(SELF_SRCS EndpointUtil.cpp DynamicEndpoint.cpp WebsockUtil.cpp WebsocketSink.cpp Webserver.cpp) + +xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies + +xo_dependency(${SELF_LIB} reactor) +xo_dependency(${SELF_LIB} webutil) + +# note: changes to xo_dependency() calls here +# must coordinate with find_dependency() calls in +# xo-websock/cmake/websockConfig.cmake.in diff --git a/src/websock/DynamicEndpoint.cpp b/src/websock/DynamicEndpoint.cpp new file mode 100644 index 00000000..3df46d47 --- /dev/null +++ b/src/websock/DynamicEndpoint.cpp @@ -0,0 +1,146 @@ +/* file DynamicEndpoint.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "DynamicEndpoint.hpp" + +namespace xo { + using xo::web::Alist; + using xo::fn::CallbackId; + using xo::ref::rp; + + namespace web { + DynamicEndpoint::DynamicEndpoint(std::string uri_pattern, + HttpEndpointFn http_fn, + StreamSubscribeFn subscribe_fn, + StreamUnsubscribeFn unsubscribe_fn) + : uri_pattern_{std::move(uri_pattern)}, + http_fn_{std::move(http_fn)}, + subscribe_fn_{std::move(subscribe_fn)}, + unsubscribe_fn_{std::move(unsubscribe_fn)} + { + std::string r_pat; + + /* 1st pass -- construct pattern regex .uri_regex + * to identify urls that belong to this endpoint + * + * using regex like: + * \$\{[[:alnum:]]+\} + */ + { + std::regex var_rgx("\\$\\{[[:alnum:]]+\\}"); + + /* e.g. if .uri_pattern: + * /fixed/stem/${a}/more/fixed/stuff/${b} + * then want r_pat: + * /fixed/stem/[[:alnum:]]+/more/fixed/stuff/[[:alnum:]]+ + * to find values pattern variables like ${a}, ${b} + */ + std::regex_replace(std::back_inserter(r_pat), + this->uri_pattern_.begin(), + this->uri_pattern_.end(), + var_rgx, + std::string("([[:alnum:]]+)")); + + this->uri_regex_ = std::regex(r_pat); + } + + /* 2nd pass -- identify pattern variables */ + { + /* regex for: + * \$\{([[:alnum:]]+)\} + * use to match input like + * ${apple} + * and also extract the variable name + * apple + */ + std::regex var_rgx("\\$\\{([[:alnum:]]+)\\}"); + std::smatch match; + + std::string subject = this->uri_pattern_; + + /* if subject like + * /fixed/stem/${a}/more/fixed/stuff/${b} + * extract + * ["a", "b"] + * + * for + * /fixed/stem/${a}/more/fixed/stuff/${b}/${a} + * also extract + * ["a", "b"] + * i.e. avoid extracting the same variable name twice + */ + while (std::regex_search(subject, match, var_rgx)) { + std::string v = match[1]; + + bool present_flag = false; + + for (auto const & x : this->var_v_) { + if (x == v) { + present_flag = true; + break; + } + } + + if (!present_flag) + this->var_v_.push_back(match[1]); + + subject = match.suffix().str(); + } + } + } /*ctor*/ + + void + DynamicEndpoint::http_response(std::string const & incoming_uri, + std::ostream * p_os) const + { + /* send this uri argument list callback. + * contains variables extracted from .uri_pattern + * (variables surrounded by ${...}) + */ + Alist alist; + + /* extract pattern variables in uri + * c.f. 2nd pass in DynamicEndpoint.ctor + */ + std::smatch match; + std::string subject = incoming_uri; + + /* if subject like + * /fixed/stem/apple/more/fixed/stuff/beagle + * with .uri_pattern + * /fixed/stem/${a}/more/fixed/stuff/${b} + * then we have .uri_regex + * /fixed/stem/([[:alnum:]]+)/more/fixed/stuff/([[:alnum:]]+) + * use this to extract values for keys in .var_v, + * in the same order + */ + if (std::regex_match(subject, match, this->uri_regex_)) { + for (size_t i = 0, n = this->var_v_.size(); ivar_v_[i]; + std::string i_value = match[1+i]; + + alist.push_back(i_name, i_value); + } + } + + this->http_fn_(incoming_uri, alist, p_os); + } /*http_response*/ + + CallbackId + DynamicEndpoint::subscribe(std::string const & /*incoming_uri*/, + rp const & ws_sink) const + { + return this->subscribe_fn_(ws_sink); + } /*subscribe*/ + + void + DynamicEndpoint::unsubscribe(CallbackId id) const + { + return this->unsubscribe_fn_(id); + } /*unsubscribe*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end DynamicEndpoint.cpp */ diff --git a/src/websock/EndpointUtil.cpp b/src/websock/EndpointUtil.cpp new file mode 100644 index 00000000..f53dbd43 --- /dev/null +++ b/src/websock/EndpointUtil.cpp @@ -0,0 +1,38 @@ +/* file EndpointUtil.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "EndpointUtil.hpp" + +namespace xo { + namespace web { + std::string + EndpointUtil::stem(std::string const & pattern) + { + std::size_t p = 0; + do { + p = pattern.find_first_of("$", p); + + if ((p != std::string::npos) && (pattern[p+1] == '{')) { + /* fixed stem is chars [0 .. p-1], i.e. 1st p characters */ + break; + } + + if (p != std::string::npos) { + /* skip to next '$' */ + ++p; + } + } while (p != std::string::npos); + + if (p == std::string::npos) { + /* pattern has no variable components */ + return pattern; + } else { + return pattern.substr(0, p); + } + } /*stem*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end EndpointUtil.cpp */ diff --git a/src/websock/Webserver.cpp b/src/websock/Webserver.cpp new file mode 100644 index 00000000..6e1dbdb1 --- /dev/null +++ b/src/websock/Webserver.cpp @@ -0,0 +1,1939 @@ +/* @file Webserver.cpp + * + * Webserver + websocket container/scaffold. + * Originally adapted from libwebsocket example + * + * 19aug2022 + * This version probably overly rigid. + * Initial goal is to adpat example application so that: + * a. it works as a library + * b. doesn't hijack main thread + * + * Subsequently, want to wrap to demonstrate library working from within python + * + * In the meantime, adopting example code as-is without looking closely + * at globals/singletons + * + */ + +#include "Webserver.hpp" +#include "WebsocketSink.hpp" +#include "WebsockUtil.hpp" +#include "WsSafetyToken.hpp" +#include "DynamicEndpoint.hpp" +#include "xo/printjson/PrintJson.hpp" +#include // for Json::Reader, to parse json input +#include +#include +#include +#include +#include + +namespace xo { + using xo::web::Alist; + using xo::reactor::AbstractSink; + using xo::json::PrintJson; + using xo::fn::CallbackId; + using xo::ref::rp; + using xo::scope; + using xo::xtag; + + namespace web { + char const * + RunstateUtil::runstate_descr(Runstate x) + { +# define CASE(x) case Runstate::x: return #x + switch(x) { + CASE(stopped); + CASE(stop_requested); + CASE(running); + } +# undef CASE + + return "???"; + } /*runstate_descr*/ + + /* both websocket and appl thread can obtain this token. + * see WebsocketSessionRecd. Posession of this token is evidence + * caller holds WebsocketSessionRecd.mutex + */ + class WsSessionSafetyToken : public SafetyToken { + private: + friend class WebsocketSessionRecd; + + private: + /* only WebsocketSessionRecd should construct this + * mutex argument present just to alert reader + */ + WsSessionSafetyToken(std::unique_lock const &) {} + }; /*WsSessionSafetyToken*/ + + + namespace { + /* one of these is created for each client connecting to us */ + + struct OutputBuffer; + + /* editor bait: + * WebserverImpl::send_text() + * ws_pss + * + * NOTE: + * 1. per_session_data__http instances are created by libwebsocket library. + * since that's implemented in C, ctor/dtors won't be invoked for this class + */ + struct per_session_data__minimal { + public: + /* output state; allocated as bona fide c++ object */ + OutputBuffer * output_buf_; + }; /*per_session_data__minimal*/ + + /* one of these created for each message */ + + /* output destined for a particular websocket. + * 'struct msg' that folllows is a POD C struct inherited from + * libwebsocket example code; intend to retire that. + * + * An OutputMsg instance sends bytes [lo..hi), + * using as many trips as necessary + * + * +---...---+---...--------+ + * | LWS_PRE | text payload | + * +---...---+---...--------+ + * ^ ^ ^ + * .buf .text() text() + text.size() + * + * |<---A--->|<------B------->| + * |<------------C----------->| + * + * A: (LWS_PRE bytes) populated by ::lws_write(), in particular encodes .buf_z + * B: (.text_z bytes) WebserverImpl passes this range to ::lws_write() + * C: (LWS_PRE + .text_z bytes) ::lws_write() actually sends this range + * + * Note trailing null isn't required, since length is explicitly sent + */ + struct OutputBuffer { + public: + OutputBuffer(uint32_t session_id) : session_id_{session_id} {} + ~OutputBuffer() = default; + + uint32_t session_id() const { return session_id_; } + struct lws * wsi() const { return wsi_; } + + /* non-const access required. + * lws_write() will prepend headers in .buf_v[0..LWS_PRE-1] + */ + unsigned char * text() { return &(buf_v_[LWS_PRE]); } + unsigned char const * text() const { return &(buf_v_[LWS_PRE]); } + size_t text_z() const { return text_z_; } + + std::string_view text_view() const { + return std::string_view((char const *)(this->text()), + this->text_z()); + } + + bool is_busy() const { return this->sent_seq_ < this->stored_seq_; } + bool is_idle() const { return this->sent_seq_ == this->stored_seq_; } + + void establish_wsi(struct lws * wsi) { this->wsi_ = wsi; } + + bool is_writeable(WsSafetyToken const &) const { return is_writeable_; } + void set_is_writeable(bool x, WsSafetyToken const &) { is_writeable_ = x; } + + /* caller must hold WebsocketSessionRecd.mutex; + * evidenced by wsession_token + */ + void store_message(uint32_t msg_seq, + std::string const & text, + WsSessionSafetyToken const & wsession_token) { + scope log(XO_ENTER0(info)); + + wsession_token.verify(); + + if (sent_seq_ != stored_seq_) { + log && log("store_message: attempt storing new msg_seq but sent_seq!=stored_seq", + xtag("sent_seq", sent_seq_), + xtag("stored_seq", stored_seq_), + xtag("msg_seq", msg_seq)); + assert(false); + } + + size_t req_z = LWS_PRE + text.size(); + + if (this->buf_v_.size() < req_z) + this->buf_v_.resize(req_z); + + this->text_z_ = text.size(); + + ::memcpy(&(this->buf_v_[LWS_PRE]), text.c_str(), this->text_z_); + + log && log(xtag("buf", (void*)&(this->buf_v_[0])), + xtag("msg_seq", msg_seq), + xtag("text", text), + xtag("text.size", text.size()), + xtag("req_z", req_z)); + + this->stored_seq_ = msg_seq; + } /*store_message*/ + + int lws_write_aux(WsSafetyToken const & ws_safety_token) + { + scope log(XO_ENTER0(info)); + + ws_safety_token.verify(); + + this->set_is_writeable(false, ws_safety_token); + + log && log("write to websocket", + xtag("wsi", (void*)wsi_), + xtag("text_z", this->text_z())); + log && log(xtag("text", this->text_view())); + + /* 1. notice we allowed for LWS_PRE in the payload already; + * this is mandatory for LWS_WRITE_TEXT. + * 2. LWS_WRITE_TEXT requires valid utf-8 payload + * 3. lws_write() writes entire contents, using + * multiple network writes if necessary. + * Application side can ignore the possibility of partial writes. + */ + int m = ::lws_write(this->wsi_, + this->text(), + this->text_z(), + LWS_WRITE_TEXT); + + if (m < (int)this->text_z()) { + /* note: first time we observed this, browser console + * showed that entire message was eventually received, + * (though not if we exit() before returning) + */ + lwsl_user("lws_write_aux: PARTIAL WRITE: session=[%u], m=lws_write(z) with msession_id_, + m, + this->text_z()); + + /* 23sep2022: consistent with observed behavior: + * - lws will write remainder of message + * - lws will call appl via LWS_CALLBACK_SERVER_WRITEABLE + * once write has been completed + * according to docs lws buffers message -- if true, + * probably pay for message to be copied + */ + return 0; + } + + this->lws_write_completion(ws_safety_token); + + return m; + } /*lws_write_aux*/ + + /* call this after successfully sending a message */ + void lws_write_completion(WsSafetyToken const & ws_safety_token) { + /* session now writeable again. either: + * - lws_write(z) successful + * - lws_write(z) incomplete, followed by lws callback + * with reason = LWS_CALLBACK_SERVER_WRITEABLE + */ + this->set_is_writeable(true, ws_safety_token); + + /* message completely written */ + this->sent_seq_ = this->stored_seq_; + } /*lws_write_completion*/ + + private: + /* identifies websocket session associated with this buffer + * established permanently in ctor + */ + uint32_t session_id_; + + /* opaque pointer; owned by libwebsocket + identifies this session. + * established once (per websocket session) from LWS_CALLBACK_ESTABLISHED + */ + struct lws * wsi_ = nullptr; + + /* ::lws_write() takes responsibility for writing and buffering full message; + * IIU docs that means it doesn't return until full write has completed; + * this suggests it may also make reentrant callbacks for other sessions, + * while an incomplete call to lws_write() is on the stack. + * + * set .is_writeable to false during lws_write() calls, + * so that application threads can refrain from attempting nested + * lws_write() calls for the same session. + */ + bool is_writeable_ = false; + + /* seq# of last message sent using this buffer; .sent_seq chases .stored_seq */ + uint32_t sent_seq_ = 0; + /* seq# of last message stored using this buffer */ + uint32_t stored_seq_ = 0; + + /* buffer for outbound text. + * using the first LWS_PRE + .text_z bytes. + * 1st LWS_PRE bytes owned by lws library, must not touch these + */ + std::vector buf_v_; + size_t text_z_ = 0; + }; /*OutputBuffer*/ + + /* + * Unlike ws, http is a stateless protocol. This pss only exists for the + * duration of a single http transaction. With http/1.1 keep-alive and + * http/2, that is unrelated to (shorter than) the lifetime of the network + * connection. + * + * NOTE + * 1. per_session_data__http instances are created by libwebsocket library. + * since that's implemented in C, we need to arrange for manual initialization + * 2. since libwebsocket implemented in C, we don't expect auto-initialization + * or constructors to be invoked. + * 3. libwebsocket gives us several alternatives for organizing resource + * allocation. We use these callback reasons: + * LWS_CALLBACK_HTTP_BIND_PROTOCOL for setup, allocate .output_ss + * LWS_CALLBACK_HTTP_DROP_PROTOCOL for teardown free .output_ss + */ + struct per_session_data__http { + int test; + /* store http reply in .output_str */ + std::string * output_str; + }; + + /* one of these is created for each vhost our protocol is used with + * + * NOTE + * 1. per_vhost_data__minimal instances are created by libwebsocket library. + * since that's implemented in C, ctors/dtors aren't used here + * + * editor bait: vhd + */ + struct per_vhost_data__minimal { + struct lws_context * context; + struct lws_vhost * vhost; + const struct lws_protocols * protocol; + + struct per_session_data__minimal * pss_list; /* linked-list of live pss*/ + + uint32_t next_session_id_; + + //struct msg amsg; /* the one pending message... */ + //int current; /* the current message number we are caching */ + }; /*per_vhost_data__minimal*/ + } /*namespace*/ + + /* bookkeeping record for a websocket subscription. */ + class WebsocketSubscriptionRecd { + public: + WebsocketSubscriptionRecd(std::string const & incoming_uri, + DynamicEndpoint * endpoint, + rp const & ws_sink) + : incoming_uri_{incoming_uri}, + endpoint_{endpoint}, + ws_sink_{ws_sink} + {} + + void subscribe() { + this->callback_id_ = this->endpoint_->subscribe(this->incoming_uri_, + this->ws_sink_); + } /*subscribe*/ + + void unsubscribe() { + this->endpoint_->unsubscribe(this->callback_id_); + } /*unsubscribe*/ + + private: + /* original subscription url */ + std::string incoming_uri_; + /* endpoint that matched .subscribe_cmd + * (see WebserverImpl.stream_map) + */ + DynamicEndpoint * endpoint_ = nullptr; + /* id created when subscription established + * (see CallbackSetImpl.add_callback()) + */ + CallbackId callback_id_; + /* sink established to receive (& forward) events on behalf + * of this subscription. application code writes to this sink. + */ + rp ws_sink_; + }; /*WebsocketSubscriptionRecd*/ + + /* bookkeeping record for a websocket session. + * WebserverImpl (below) keeps exactly one of these + * for each active websocket session + */ + class WebsocketSessionRecd { + public: + WebsocketSessionRecd(OutputBuffer * output_buf) : output_buf_{output_buf} { + assert(this->output_buf_); + } + + bool is_output_busy() const { + return (this->output_buf_ + && this->output_buf_->is_busy()); + } + bool outbound_q_empty() const { return this->outbound_q_.empty(); } + + void subscribe_endpoint(std::string const & incoming_cmd, + DynamicEndpoint * endpoint, + rp const & ws_sink) { + + scope log(XO_ENTER0(info), + xtag("incoming_cmd", incoming_cmd)); + + std::unique_ptr sub_recd_uptr + (new WebsocketSubscriptionRecd(incoming_cmd, + endpoint, + ws_sink)); + WebsocketSubscriptionRecd * sub_recd_addr = sub_recd_uptr.get(); + + { + std::lock_guard lock(this->mutex_); + + this->active_subscription_v_.push_back(std::move(sub_recd_uptr)); + } + + /* note: need to call with lock dropped, + * since subscribe may in principle call WebserverImpl.send_text() + */ + if (sub_recd_addr) + sub_recd_addr->subscribe(); + } /*subscribe_endpoint*/ + + void send_text(std::string text) { + scope log(XO_ENTER0(info)); + + std::unique_lock lock(this->mutex_); + + if (!(this->output_buf_)) { + log && log("ws_pss.output_buf not present -> exit"); + } else if (this->is_output_busy()) { + log && log("ws_pss.output_msg busy, enqueue"); + + /* previous message already in progress, enq or drop */ + this->enqueue_text(std::move(text), + WsSessionSafetyToken(lock)); + + /* this message will eventually get sent via + * .lws_write_pending_traffic() + */ + } else { + /* send message now! */ + log && log("output_msg idle, send now"); + + this->prepare_outbound_message(std::move(text), + WsSessionSafetyToken(lock)); + + /* can release lock, won't be using for remainder of this function */ + lock.unlock(); + + lws_context * lws_cx = ::lws_get_context(this->output_buf_->wsi()); + + /* interrupt libwebsocket event loop. + * will send 'wait cancelled' event to all sockets. + * + * Actually, after testing -- looks like this sends to one wsi per protocol: + * basically to the "listening" wsi, not to websocket "session" wsi + */ + ::lws_cancel_service(lws_cx); + + /* NOTE: web documentation seems to suggest using lws_callback_on_writable(): + * + * trigger call from websocket thread to send data. + * will cause reentry via websocket thread into + * WebserverImpl::notify_minimal() + * with reason=LWS_CALLBACK_SERVER_WRITEABLE + * + * ^^^ Hmm, doesn't seem to work this way. + * Suspect this would only work if socket currently + * in non-writeable state. + */ + //lws_callback_on_writable(ws_pss->wsi); + } + } /*send_text*/ + + /* write some pending traffic from lws event loop + * + * Require: + * - MUST be invoked from lws event loop, for threadsafety; + * ws_safety_token provides evidence of this + */ + void lws_write_pending(WsSafetyToken const & ws_safety_token) { + scope log(XO_ENTER0(info)); + + log && log(xtag("output_buf", (void*)this->output_buf_)); + +#ifdef OBSOLETE + per_session_data__minimal * ws_pss = this->ws_pss_; + + if (!ws_pss) { + lscope.log("null ws_pss, exit"); + return; + } +#endif + + if (!(this->output_buf_)) { + /* output message buffer not established, + * implies nothing sent yet + */ + log && log("output_msg either not established or destroyed, exit"); + return; + } + + /* loop until no queued messages for this session */ + for (;;) { + if (!(this->output_buf_->is_writeable(ws_safety_token))) { + /* call to lws_write() already in progress */ + log && log("output_buf not writeable (bc lws_write in progress)"); + return; + } + + if (this->output_buf_->is_idle()) { + /* already up-to-date for this session */ + log && log("output idle (up-to-date)"); + return; + } + + this->output_buf_->lws_write_aux(ws_safety_token); + + /* if there are any appl messages queued, prepare to send another one */ + if (this->outbound_q_.empty()) { + /* all caught up, nothing left to send */ + log && log("up-to-date after write"); + } else { + std::unique_lock lock(this->mutex_); + + std::string text(this->dequeue_text(WsSessionSafetyToken(lock))); + + this->prepare_outbound_message(std::move(text), + WsSessionSafetyToken(lock)); + } + } + } /*lws_write_pending*/ + + /* threadsafe */ + void unsubscribe_all() { + std::lock_guard lock(this->mutex_); + + /* also drop .output_buf, + * to short-circuit any subsequent attempts to use .lws_write_pending() + * (which will happen in response to LWS_CALLBACK_EVENT_WAIT_CANCELLED + * on any session) + */ + for (auto & sub_ptr : this->active_subscription_v_) + sub_ptr->unsubscribe(); + + this->output_buf_ = nullptr; + this->active_subscription_v_.clear(); + } /*unsubscribe_all*/ + + private: + uint32_t generate_msg_seq() { return ++(this->last_msg_seq_); } + + /* enqueue application-level message. + * use this when .ws_pss.outbound_buf is busy + */ + void enqueue_text(std::string text, + WsSessionSafetyToken const & /*wss_token*/) { + this->outbound_q_.push_back(std::move(text)); + } /*enqueue_text*/ + + /* remove a deferred message from .outbound_q, + * and return it. This can happen if output becomes + * available after being write-blocked + */ + std::string dequeue_text(WsSessionSafetyToken const & /*wss_token*/) { + assert(!this->outbound_q_.empty()); + + std::string retval = std::move(this->outbound_q_.front()); + + this->outbound_q_.pop_front(); + + return retval; + } /*dequeue_text*/ + + /* prepare outbound message for sending in contiguous memory; + * in particular prepends header. + * + * this can be called from either websocket or appl thread, + * so needs to be threadsafe. + */ + void prepare_outbound_message(std::string text, + WsSessionSafetyToken const & wss_token) { + scope log(XO_ENTER0(info)); + + /* sequence# for this outbound message */ + uint32_t msg_seq = this->generate_msg_seq(); + + this->output_buf_->store_message(msg_seq, + std::move(text), + wss_token); + + /* now ws_pss->output_msg_->is_busy() */ + log && log("staged next write", + xtag("wsi", (void*)this->output_buf_->wsi()), + xtag("text_z", this->output_buf_->text_z())); + } /*prepare_outbound_message*/ + + private: + /* output destined for this session + * libws (via per_session_data__minimal) also points to + * .output_buf + */ + OutputBuffer * output_buf_ = nullptr; + /* protects .active_subscription_v, .last_msg_seq, .outbound_q */ + std::mutex mutex_; + /* active subscriptions established by this session */ + std::vector> active_subscription_v_; + /* generate seq#'s for outgoing messages */ + uint32_t last_msg_seq_ = 0; + /* when new outgoing message appears: + * 1. if .pss->output_msg empty, allocate it and store message there; + * invoke lws_callback_on_writeable(.pss->wsi) to get message sent asap + * 2. otherwise sending a previous message is in-progress; + * put outgoing message to the back of .outbound_q + */ + std::deque outbound_q_; + }; /*WebsocketSessionRecd*/ + + using EndpointMap = std::unordered_map>; + + /* defined in this translation unit, after WebserverImpl */ + class WebserverImplWsThread; + + class WebserverImpl : public Webserver { + public: + WebserverImpl(WebserverConfig const & ws_config, + rp const & pjson) + : ws_config_{ws_config}, + pjson_{pjson}, + readjson_{Json::CharReaderBuilder().newCharReader()}, + interrupt_flag_{false}, + state_{Runstate::stopped} + { + } /*ctor*/ + + virtual ~WebserverImpl() { + /* if webserver is running, initiate shutdown. + * webserver thread will eventually exit + */ + this->stop_webserver(); + /* wait for shutdown to complete */ + this->join_webserver(); + } /*dtor*/ + + virtual void run() = 0; + + // ----- Inherited from Webserver ----- + + virtual Runstate state() const override { return state_; } + virtual void register_http_endpoint(HttpEndpointDescr const & endpoint) override; + virtual void register_stream_endpoint(StreamEndpointDescr const & endpoint) override; + virtual void start_webserver() override; + virtual void interrupt_stop_webserver() override; + virtual void stop_webserver() override; + virtual void join_webserver() override; + + protected: + void set_lws_log_level() { + lws_set_log_level(LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE + /* for LLL_ verbosity above NOTICE to be built into + * lws, lws must have been configured and built with + * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ + /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ + /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ + /* | LLL_DEBUG */, + NULL); + } /*set_lws_log_level*/ + +#if defined(LWS_WITH_PLUGINS) + void init_pvo(lws_protocol_vhost_options * p_pvo) { + /* {next, options, name, value} */ + *p_pvo = {NULL, NULL, "lws-minimal", ""}; + } /*init_pvo*/ +#endif + + /* called once during webserver initialization; + * identifies protocols (channels) that libws is expected to support + */ + virtual void init_protocols(std::vector * p_v) = 0; + + void init_mount_dynamic(lws_http_mount * p_mount) { + *p_mount = { + /* .mount_next */ NULL, /* linked-list "next" */ + /* .mountpoint */ "/dyn", /* mountpoint URL */ + /* .origin */ NULL, /* protocol */ + /* .def */ NULL, + /* .protocol */ "http", + /* .cgienv */ NULL, + /* .extra_mimetypes */ NULL, + /* .interpret */ NULL, + /* .cgi_timeout */ 0, + /* .cache_max_age */ 0, + /* .auth_mask */ 0, + /* .cache_reusable */ 0, + /* .cache_revalidate */ 0, + /* .cache_intermediaries */ 0, + /* .origin_protocol */ LWSMPRO_CALLBACK, /* dynamic */ + /* .mountpoint_len */ 4, /* char count */ + /* .basic_auth_login_file */ NULL, + }; + } /*init_mount_dynamic*/ + + void init_mount_static(lws_http_mount const * dynamic, + lws_http_mount * p_mount) { + /* default mount serves the URL space from ./mount-origin */ + *p_mount = { + /* .mount_next */ dynamic, /* linked-list "next" */ + /* .mountpoint */ "/", /* mountpoint URL */ + /* .origin */ "./mount-origin", /* serve from dir */ + /* .def */ "index.html", /* default filename */ + /* .protocol */ NULL, + /* .cgienv */ NULL, + /* .extra_mimetypes */ NULL, + /* .interpret */ NULL, + /* .cgi_timeout */ 0, + /* .cache_max_age */ 0, + /* .auth_mask */ 0, + /* .cache_reusable */ 0, + /* .cache_revalidate */ 0, + /* .cache_intermediaries */ 0, + /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */ + /* .mountpoint_len */ 1, /* char count */ + /* .basic_auth_login_file */ NULL, + }; + } /*init_mount_static*/ + + void init_retry(lws_retry_bo_t * p_retry) { + p_retry->secs_since_valid_ping = 3; + p_retry->secs_since_valid_hangup = 10; + } /*init_retry*/ + + /* requires: + * - .pvo initialized, see .init_pvo() + * - .protocol_v[] initialized, see .init_protocols() + * - .mount_dynamic initialized, see .init_mount_dynamic() + * - .mount_static initialized, see .init_mount_static() + * - .retry initialized, see .init_retry() + */ + void init_cx_config(lws_context_creation_info * p_cx_config) { + ::memset(p_cx_config, 0, sizeof(*p_cx_config)); + p_cx_config->port = this->ws_config_.port(); + p_cx_config->vhost_name = "localhost"; + p_cx_config->pvo = &(this->pvo_); + p_cx_config->protocols = this->protocol_v_.data(); + p_cx_config->mounts = &(this->mount_static_); + /* userdata -- accessible from context with lws_context_user() */ + p_cx_config->user = (void*)this; + +#if defined(LWS_WITH_TLS) + if (this->ws_config_.tls_flag()) { + lwsl_user("Server using TLS\n"); + p_cx_config->options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + p_cx_config->ssl_cert_filepath = "localhost-100y.cert"; + p_cx_config->ssl_private_key_filepath = "localhost=100y.key"; + } +#endif + + if (this->ws_config_.host_check_flag()) { + p_cx_config->options |= LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK; + } + + if (this->ws_config_.use_retry_flag()) { + p_cx_config->retry_and_idle_policy = &(this->retry_); + } + } /*init_cx_config*/ + + /* check for a DynamicEndpoint stored under stem; + * if found, invoke it on incoming_uri to respond + * + * return. true iff stem matched a dynamic endpoint; + */ + DynamicEndpoint * lookup_dynamic_http_stem(std::string const & stem); + + /* write dynamic http response for incoming_uri, on *p_os + * incoming_uri will be suffix of original uri from browser, + * following dynamic mount point [/dyn]. + * see .init_mount_dynamic() + */ + void dynamic_http_response(std::string const & incoming_uri, + std::ostream * p_os); + + /* act on incoming websocket command + * expecting json like + * {"command": "subscribe", "stream": "uls"} + */ + void perform_ws_cmd(uint32_t session_id, + std::string_view incoming_svw); + +#ifdef DEFINED_BUT_NOT_USED + /* called from libwebsocket thread when session manager + * (aka "virtual host") is created for the websocket protocol + */ + void notify_vhd(per_vhost_data__minimal * vhd); +#endif + /* called from libwebsocket thread when creating a new websocket session */ + void notify_ws_session_open(OutputBuffer * output_buf, + per_vhost_data__minimal * vhd, + WsSafetyToken const & ws_safety_token); + /* called from libwebsocket thread whenever a websocket session is closed */ + void notify_ws_session_close(OutputBuffer * output_buf, + per_vhost_data__minimal * vhd, + WsSafetyToken const & ws_safety_token); + + /* send text to the websocket session identified by session_id */ + void send_text(uint32_t session_id, + std::string text) override; + + /* from lws event loop, write any pending outbound traffic + * see .pending_session_q + */ + void lws_write_pending_traffic(WsSafetyToken const & ws_safety_token); + + protected: + /* callback for http protocol */ + static int notify_dynamic_http(struct lws * wsi, + lws_callback_reasons reason, + void * user_data, + void * incoming_uri, + size_t len); + + protected: + /* see WebserverImplWsThread below, for methods + * that are exclusive to libws thread + */ + + /* initial configuration for embedded webserver */ + WebserverConfig ws_config_; + + /* json printer (w/ plugins for reflected types) */ + rp pjson_; + + /* json reader */ + std::unique_ptr readjson_; + + /* --- 1. LWS configuration stuff (set once) ---*/ + +#if defined(LWS_WITH_PLUGINS) + /* protocols listed here will "bind to vhost". + * (I don't know for sure what this means -- it's a magic spell for now) + * + */ + lws_protocol_vhost_options pvo_; +#endif + + /* protocols to accept for this webserver */ + std::vector protocol_v_; + + /* mount point for dynamic urls + * (these will be served by executing c++ code, + * instead of serving static disk files) + */ + lws_http_mount mount_dynamic_; + /* mount point for static urls + * (serve static files from file system) + */ + lws_http_mount mount_static_; + + /* retry settings + * (not sure how these are used) + */ + lws_retry_bo_t retry_; + + /* configuration record for lws context + * AFAIK require lifetime >= lws_context + */ + lws_context_creation_info cx_config_; + + /* runtime state owned by LWS library + * can get application-determined user data from a lws_context by + * lws_context_user(.lws_cx) + */ + lws_context * lws_cx_ = nullptr; + + /* --- 2. startup/shutdown control --- */ + + /* set this to true to prevent further service loop iteration */ + std::atomic interrupt_flag_; + + /* protects .state */ + std::mutex mutex_; + std::condition_variable cond_; + + /* valid states + * + * .state .thread_ptr + * ----------------------------------------------- + * running thread in WebserverImpl::run() + * stop_requested thread in WebserverImpl::run() + * stopped nullptr + */ + Runstate state_; + std::unique_ptr thread_ptr_; + + /* --- 3. plugin state (writable while server runs) --- */ + + /* map :: stem->http_fn, + * where + * stem = "longest non-variable URI prefix" + * + * use .register_http_endpoint() to insert a new URI into this map + * + * this map used for http endpoints + */ + EndpointMap stem_map_; + /* map :: stem->subscribe_fn + * where + * stem = "longest non-variable URI prefix" + * + * use .register_stream_endpoint() to insert a new URI into this map + * + * this map used for stream endpoints + */ + EndpointMap stream_map_; + + /* --- 4. libwebsocket session manager --- */ + + /* websocket-associated libwebsocket data. + * created by libwebsocket; our appl code informed via + * LWS_CALLBACK_PROTOCOL_INIT + * + * list of all active sessions is in .ws_vhd->pss_list + * (see LWS_CALLBACK_PROTOCOL_INIT, LWS_CALLBACK_ESTABLISHED, LWS_CALLBACK_CLOSED) + * + * can visit sessions with macros: + * lws_start_foreach_llp(struct per_session_data__minimal **, ppss, vhd->pss_list) { + * ..do stuff with (*ppss)->wsi for example.. + * } lws_end_foreach_llp(ppss, pss_list); + */ + per_vhost_data__minimal * ws_vhd_ = nullptr; + + /* indexed by session id# (see per_session_data__minimal.session_id) + * .session_v.size() = {max #of simultaneously-open websocket sessions}. + * may contain empty slots. if .session_v[i] is empty, + * then i appears in .free_session_id_v[], i..e .free_session_id_v[j]=i for some j + */ + std::vector> session_v_; + + /* When a session closes, its session id becomes available. + * track such session ids here, so they can be recycled. + * want to recycle because they're indexes into .session_v[], + * and we don't want that to grow without bound + */ + std::vector free_session_id_v_; + + }; /*WebserverImpl*/ + + void + WebserverImpl::register_http_endpoint(HttpEndpointDescr const & endpoint_descr) + { + auto endpoint = DynamicEndpoint::make_http(endpoint_descr.uri_pattern(), + endpoint_descr.endpoint_fn()); + + this->stem_map_[endpoint->stem()] = std::move(endpoint); + } /*register_http_endpoint*/ + + void + WebserverImpl::register_stream_endpoint(StreamEndpointDescr const & endpoint_descr) + { + auto endpoint = DynamicEndpoint::make_stream(endpoint_descr.uri_pattern(), + endpoint_descr.subscribe_fn(), + endpoint_descr.unsubscribe_fn()); + + this->stream_map_[endpoint->stem()] = std::move(endpoint); + } /*register_stream_endpoint*/ + +#ifdef DEFINED_BUT_NOT_USED + void + WebserverImpl::notify_vhd(per_vhost_data__minimal * vhd) + { + this->ws_vhd_ = vhd; + } /*notify_vhd*/ +#endif + + void + WebserverImpl::notify_ws_session_open(OutputBuffer * output_buf, + per_vhost_data__minimal * vhd, + WsSafetyToken const & ws_safety_token) + { + ws_safety_token.verify(); + + uint32_t new_id = output_buf->session_id(); + + if (this->session_v_.size() <= new_id) + this->session_v_.resize(new_id + 1); + + this->session_v_[new_id].reset(new WebsocketSessionRecd(output_buf)); + + /* control comes here when a new websocket session is created, + * after LWS_CALLBACK_HTTP_BIND_PROTOCOL + */ + output_buf->set_is_writeable(true, ws_safety_token); + + /* compute next available session id + store in vhost struct */ + + if (this->free_session_id_v_.empty()) { + /* generate a new session id */ + uint32_t id = this->session_v_.size(); + + vhd->next_session_id_ = id; + } else { + /* recycle a previously-used session id */ + uint32_t id = this->free_session_id_v_[this->free_session_id_v_.size() - 1]; + this->free_session_id_v_.pop_back(); + + vhd->next_session_id_ = id; + } + } /*notify_ws_session_open*/ + + void + WebserverImpl::notify_ws_session_close(OutputBuffer * output_buf, + per_vhost_data__minimal * /*vhd*/, + WsSafetyToken const & ws_safety_token) + { + scope log(XO_ENTER0(info)); + + log && log("enter", + xtag("this", (void*)this), + xtag("output_buf", (void*)output_buf)); + + assert(output_buf->session_id() < this->session_v_.size()); + + ws_safety_token.verify(); + + WebsocketSessionRecd * ws_session_recd + = this->session_v_[output_buf->session_id()].get(); + + if (ws_session_recd) { + ws_session_recd->unsubscribe_all(); + } + + this->free_session_id_v_.push_back(output_buf->session_id()); + } /*notify_ws_session_close*/ + + /* note: to access lws_protocols.user, + * would use lws_get_protocol(wsi)->user + */ + int + WebserverImpl::notify_dynamic_http(struct lws * wsi, + lws_callback_reasons reason, + void * user_data, + void * incoming_data, + size_t len) + { + lws_context * lws_cx = lws_get_context(wsi); + void * cx_user_data = lws_context_user(lws_cx); + WebserverImpl * websrv = reinterpret_cast(cx_user_data); + + struct per_session_data__http * http_pss + = reinterpret_cast(user_data); + + lwsl_user("notify_dynamic_http: enter: reason %d (%s): lws_cx %p websrv %p\n", + reason, + WebsockUtil::ws_callback_reason_descr(reason), + lws_cx, + websrv); + + /* scratch space for http header + * (probably only need LWS_PRE here, I think the +256 debris + * from o.g. example) + */ + uint8_t buf[LWS_PRE + 256]; + uint8_t * start = &buf[LWS_PRE]; + uint8_t * p = start; + uint8_t * end = &buf[sizeof(buf) - 1]; + + switch (reason) { + case LWS_CALLBACK_HTTP: + { + /* incoming_uri contains the uri suffix following our mountpoint [/dyn] + * (see WebserverImpl.init_mount_dynamic()). + * + * looks like this gets spuriously invoked for non-dynamic mountpoints + * given that we serve both filesystem tree + * (in url-space at /, from dir ./mount-origin) and dynamic http (in url-space at /dyn); + * + * however output from the spurious invocation seems to be discarded + */ + char const * incoming_uri + = reinterpret_cast(incoming_data); + + assert(http_pss->output_str == nullptr); + + if (http_pss->output_str == nullptr) { + http_pss->output_str = new std::string; + } + + lwsl_user("allocate output_str [%p] in http_pss [%p]", + http_pss->output_str, http_pss); + + std::stringstream response_ss; + + assert(websrv); + + websrv->dynamic_http_response(incoming_uri, + &response_ss); + + *(http_pss->output_str) = response_ss.str(); + + lwsl_user("LWS_CALLBACK_HTTP: got response [%s]", + http_pss->output_str->c_str()); + + /* choose mime type */ + constexpr char const * c_mime_type = "application/json"; + + /* prepare and write http headers + * (do these precede &p ??) + */ + if (lws_add_http_common_headers(wsi, + HTTP_STATUS_OK, + c_mime_type, + http_pss->output_str->length(), + &p, end)) + return 1; + + if (lws_finalize_write_http_header(wsi, start, &p, end)) + return 1; + + /* write the body separately */ + lws_callback_on_writable(wsi); + + return 0; + } + + case LWS_CALLBACK_HTTP_WRITEABLE: + { + if (!http_pss || !http_pss->output_str || (http_pss->output_str->length() == 0)) + break; + + /* + * Use LWS_WRITE_HTTP (instead of LWS_WRITE_HTTP_FINAL) for intermediate writes, + * on http/2 lws uses this to understand to end the stream with this + * frame. + * + * TODO: if output is large, write it in smaller chunks. + * expecting mtu like 1500 bytes, so maybe 128k + * chunks will work well? + */ + if (lws_write(wsi, + (uint8_t *)(http_pss->output_str->c_str()), + http_pss->output_str->length(), + LWS_WRITE_HTTP_FINAL) + != static_cast(http_pss->output_str->length())) + { + return 1; + } + + /* + * HTTP/1.0 no keepalive: close network connection + * HTTP/1.1 or HTTP1.0 + KA: wait / process next transaction + * HTTP/2: stream ended, parent connection remains up + */ + if (lws_http_transaction_completed(wsi)) + return -1; + + return 0; + } + + case LWS_CALLBACK_HTTP_BIND_PROTOCOL: + { + /* from libwebsocket docs: + * By default, all HTTP handling is done in protocols[0]. + * However you can bind different protocols (by name) to different parts of the URL space using callback mounts. + * This callback occurs in the new protocol when a wsi is bound to that protocol. + * Any protocol allocation related to the http transaction processing should be created then. + * These specific callbacks are necessary because with HTTP/1.1, + * a single connection may perform a series of different transactions at different URLs, + * thus the lifetime of the protocol bind is just for one transaction, not connection. + */ + if (!http_pss) + break; + + /* although we could allocate http_pss->output_ss here, + * instead delay until LWS_CALLBACK_HTTP. + * this reduces new/delete churn, since BIND/DROP callbacks + * will get invoked on every incoming request, not just for + * dynamic http requests. + */ + http_pss->output_str = nullptr; + + lwsl_user("initialize http_pss->output_str to null in http_pss [%p]", + http_pss); + } + break; + + case LWS_CALLBACK_HTTP_DROP_PROTOCOL: + /* from libwebsocket docs: + * This is called when a transaction is unbound from a protocol. + * It indicates the connection completed its transaction and may do something different now. + * Any protocol allocation related to the http transaction processing should be destroyed. + */ + if (!http_pss) + break; + + if (http_pss->output_str) { + lwsl_user("destroy string [%p] in http_pss [%p]", + http_pss->output_str, http_pss); + + delete http_pss->output_str; + + http_pss->output_str = nullptr; /*hygiene*/ + } + + default: + break; + } + + return lws_callback_http_dummy(wsi, reason, user_data, incoming_data, len); + + } /*notify_dynamic_http*/ + + void + WebserverImpl::start_webserver() + { + switch(state_) { + case Runstate::stopped: + { + std::unique_lock lock(this->mutex_); + + this->thread_ptr_.reset(new std::thread(&WebserverImpl::run, this)); + this->state_ = Runstate::running; + } + break; + case Runstate::stop_requested: + throw std::runtime_error("webserver in stop-requested state"); + /* could invent a "restart-requested" state, I suppose */ + break; + case Runstate::running: + throw std::runtime_error("webserver already running"); + break; + } + } /*start_webserver*/ + + namespace { + DynamicEndpoint * + lookup_stem(std::string const & stem, + EndpointMap const & ep_map) + { + scope log(XO_DEBUG(true /*debug_flag*/), + xtag("stem", stem)); + + auto ix = ep_map.find(stem); + + if (ix != ep_map.end()) + return ix->second.get(); + else + return nullptr; + } /*lookup_stem*/ + + DynamicEndpoint * + lookup_pattern(std::string const & incoming_uri, + EndpointMap const & ep_map) + { + if (incoming_uri.empty()) + return nullptr; + + /* find longest prefix of incoming_uri that appears in .stem_map. + * + * 1. try the whole uri + * 2. try successively shorter prefixes of uri that end in '/' + * 3. try successively shorter prefixes of uri that do not end in '/' + */ + + /* 1. try the whole uri */ + DynamicEndpoint * endpoint = nullptr; + + endpoint = lookup_stem(incoming_uri, ep_map); + + if (!endpoint) { + /* 2. try successively shorter prefixes of uri that end in '/'. + * we already checked for the whole uri, so look for a match + * at or before the 2nd-last character + */ + if (incoming_uri.size() >= 2) { + std::string::size_type p = incoming_uri.size() - 1; + + while (!endpoint) { + p = incoming_uri.find_last_of('/', p-1); + + if (p == std::string::npos) + break; + + endpoint + = lookup_stem(incoming_uri.substr(0, p+1), ep_map); + + if (p == 0) + break; + } + } + } + + if (!endpoint) { + /* 3. try successively shorter prefixes of uri that don't end in '/'. + */ + if (incoming_uri.size() >= 2) { + std::string::size_type p = incoming_uri.size() - 2; + + while (!endpoint) { + if (incoming_uri[p] == '/') { + /* all stems ending in '/' have already been excluded */ + ; + } else { + endpoint + = lookup_stem(incoming_uri.substr(0, p+1), ep_map); + } + + if (p == 0) + break; + + --p; + } + } + } + + return endpoint; + } /*lookup_pattern*/ + } /*namespace*/ + + void + WebserverImpl::dynamic_http_response(std::string const & incoming_uri, + std::ostream * p_os) + { + DynamicEndpoint * endpoint = lookup_pattern(incoming_uri, + this->stem_map_); + + if (endpoint) { + endpoint->http_response(incoming_uri, p_os); + return; + } else { + /* if control here, no match */ + + /* or replace pss->str, pss->len with whatever dynamic content you like */ + time_t t0 = ::time(nullptr); + + *p_os << ("" + "" + "
no dynamic content for uri [") + << incoming_uri + << ("]" + " from mountpoint." + "
time: ") + << ctime(&t0) + << ""; + } + } /*dynamic_http_response*/ + + void + WebserverImpl::perform_ws_cmd(uint32_t session_id, + std::string_view incoming_cmd) + { + /* expecting input like: + * {"command": "subscribe", + * "stream": "usl"} + */ + + scope log(XO_ENTER0(info), + xtag("incoming_cmd", incoming_cmd)); + + Json::Value root; + + JSONCPP_STRING err; + bool ok = this->readjson_->parse(incoming_cmd.data(), + incoming_cmd.data() + incoming_cmd.size(), + &root, + &err); + + if (!ok) { + log && log("error: parsing failed", + xtag("incoming_cmd", incoming_cmd)); + } + + //std::cout << "WebserverImpl::perform_ws_cmd :root [" << root << "]" << std::endl; + + std::string cmd = root["cmd"].asString(); + + log && log("ws command", xtag("cmd", cmd)); + //std::cout << "WebserverImpl::perform_ws_cmd :cmd [" << cmd << "]" << std::endl; + + if (cmd == "subscribe") { + std::string stream_name = root["stream"].asString(); + + log && log("subscribe stream", xtag("stream", stream_name)); + + DynamicEndpoint * endpoint = lookup_pattern(stream_name, + this->stream_map_); + + if (endpoint) { + log && log("endpoint found"); + + /* sink to receive outbound events bound for session_id, + * for stream_name + */ + rp ws_sink + = WebsocketSink::make(this, + this->pjson_, + session_id, + stream_name); + + log && log("sink created"); + + assert(ws_sink->allow_polymorphic_source()); + assert(ws_sink->allow_volatile_source()); + + WebsocketSessionRecd * ws_recd = this->session_v_[session_id].get(); + + assert(ws_recd); + + ws_recd->subscribe_endpoint(std::string(incoming_cmd), + endpoint, + ws_sink); + } else { + log && log("endpoint not found"); + } + } + } /*perform_ws_cmd*/ + + void + WebserverImpl::interrupt_stop_webserver() + { + /* NOTE: this is threadsafe - ::lws_cancel_service() + * writes to a pipe to interrupt polling loop + */ + { + this->interrupt_flag_ = true; + + if (this->lws_cx_) { + ::lws_cancel_service(this->lws_cx_); + } + } + + std::unique_lock lock(this->mutex_); + + this->state_ = Runstate::stop_requested; + } /*interrupt_stop_webserver*/ + + void + WebserverImpl::stop_webserver() + { + std::unique_lock lock(this->mutex_); + + if(this->state_ == Runstate::running) { + this->interrupt_stop_webserver(); + } + } /*stop_webserver*/ + + void + WebserverImpl::join_webserver() { + while(true) { + std::unique_lock lock(this->mutex_); + + if (this->state_ == Runstate::stopped) + break; + + this->cond_.wait(lock); + } + + if (this->thread_ptr_) { + this->thread_ptr_->join(); + this->thread_ptr_ = nullptr; + } + } /*join_webserver*/ + + void + WebserverImpl::send_text(uint32_t session_id, + std::string text) + { + scope log(XO_ENTER0(info)); + log && log(xtag("session_id", session_id), + xtag(".session_v.size", this->session_v_.size())); + + if (session_id < this->session_v_.size()) { + WebsocketSessionRecd * p_session_recd = this->session_v_[session_id].get(); + + if (p_session_recd) + p_session_recd->send_text(text); + + //per_session_data__minimal * ws_pss = p_session_recd->ws_pss(); + + //lscope.log(xtag("ws_pss", ws_pss), + // xtag("ws_pss.wsi", ws_pss->wsi)); + + } else { + assert(false); + } + } /*send_text*/ + + void + WebserverImpl::lws_write_pending_traffic(WsSafetyToken const & ws_safety_token) + { + scope log(XO_ENTER0(info)); + + ws_safety_token.verify(); + + for (auto & session_ptr : this->session_v_) { + if (session_ptr) + session_ptr->lws_write_pending(ws_safety_token); + } + } /*lws_write_pending_traffic*/ + + /* sequester .ws_safety_token: + * it may only be used by dedicated websocket library thread + * (the unique thread that calls ::lws_service()) + */ + class WebserverImplWsThread : public WebserverImpl { + public: + WebserverImplWsThread(WebserverConfig const & ws_config, + rp const & pjson) + : WebserverImpl(ws_config, pjson) + { + scope log(XO_DEBUG(true /*debug_flag*/), + xtag("self", (void*)this)); + + this->set_lws_log_level(); +#if defined(LWS_WITH_PLUGINS) + this->init_pvo(&(this->pvo_)); +#endif + this->init_protocols(&(this->protocol_v_)); + this->init_mount_dynamic(&(this->mount_dynamic_)); + this->init_mount_static(&(this->mount_dynamic_), + &(this->mount_static_)); + this->init_cx_config(&(this->cx_config_)); + } /*ctor*/ + + /* create instance */ + static rp make(WebserverConfig const & ws_config, + rp const & pjson); + + // ----- Inherited from WebserverImpl ----- + + /* init helper */ + virtual void init_protocols(std::vector * p_v) override; + + /* run webserver. borrows calling thread, doesn't return + * until webserver stopped. + */ + virtual void run() override; + + private: + /* callback for lws-minimal protocol (websocket) */ + static int notify_minimal(struct lws * wsi, + lws_callback_reasons reason, + void * user_data, + void * incoming_uri, + size_t len); + + WsSafetyToken const & ws_safety_token() const { return ws_safety_token_; } + + private: + /* a function taking .ws_safety_token as an argument, + * announces that it is being called from the libws thread, + * i.e. reentrantly from ::lws_service() + */ + WsSafetyToken ws_safety_token_; + }; /*WebserverImplWsThread*/ + + /* 1. anything after the host:port prefix will get handled by callback_dynamic_http + * 2. host::port alone will upgrade to "lws-minimal" for websocket demo + * + * in practice p_v will be &WebserverImpl::protocol_v_ + */ + void + WebserverImplWsThread::init_protocols(std::vector * p_v) + { + /* lws_protocols: + * .name + * .callback + * .per_session_data_size + * .rx_buffer_size + * .id advertised as accessible from callback, but don't see how to use this + * .user advertised as accessible from callback, but don't see how to make this work. + * looks like libwebsocket allocates its own struct, even if .user is nonempty, + * and whether or not .per_session_data_size is 0. + * .tx_packet_size + */ + p_v->push_back({ + "http", + &WebserverImpl::notify_dynamic_http, + sizeof(struct per_session_data__http), + 0, + 0, + NULL, + 0 + }); + p_v->push_back({ + "lws-minimal", + &WebserverImplWsThread::notify_minimal, + sizeof(struct per_session_data__minimal), + 128, + 0, + NULL, + 0}); + /* mandatory end-of-array sentinel, requires by lws */ + p_v->push_back(LWS_PROTOCOL_LIST_TERM); + } /*init_protocols*/ + + /* called reentrantly from ::lws_service(), + * to do work on behalf of the websocket protocol "lws-minimal" + */ + int + WebserverImplWsThread::notify_minimal(struct lws * wsi, + lws_callback_reasons reason, + void * user_data, + void * input, + size_t input_z) + { + scope log(XO_ENTER0(info), + xtag("wsi", (void*)wsi)); + + lwsl_user("WebserverImpl::notify_minimal: enter" + ": reason %d (%s)", + reason, + WebsockUtil::ws_callback_reason_descr(reason)); + + assert(wsi); + + lws_context * lws_cx = lws_get_context(wsi); + + assert(lws_cx); + void * cx_user_data = lws_context_user(lws_cx); + + WebserverImplWsThread * websrv = reinterpret_cast(cx_user_data); + assert(websrv); + + WsSafetyToken const & ws_token = websrv->ws_safety_token(); + + struct per_session_data__minimal * ws_pss + = ((struct per_session_data__minimal *)user_data); + + lwsl_user("WebserverImpl::notify_minimal: enter" + ": reason %d (%s): wsi [%p], ws_pss [%p], lws_cx [%p] websrv [%p]\n", + reason, + WebsockUtil::ws_callback_reason_descr(reason), + wsi, + ws_pss, + lws_cx, + websrv); + + struct per_vhost_data__minimal * vhd + = ((struct per_vhost_data__minimal *) + lws_protocol_vh_priv_get(lws_get_vhost(wsi), + lws_get_protocol(wsi))); + int m; + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + { + vhd = (reinterpret_cast + (lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), + lws_get_protocol(wsi), + sizeof(struct per_vhost_data__minimal)))); + vhd->context = lws_get_context(wsi); + vhd->vhost = lws_get_vhost(wsi); + vhd->protocol = lws_get_protocol(wsi); + vhd->pss_list = nullptr; + vhd->next_session_id_ = 1; + //vhd->current = 0; + + lwsl_user("WebserverImpl::notify_minimal: vhost=%p, protocols=%p protocol.name=%s\n", + vhd->vhost, vhd->protocol, vhd->protocol->name); + } + break; + + case LWS_CALLBACK_WS_SERVER_BIND_PROTOCOL: + case LWS_CALLBACK_HTTP_BIND_PROTOCOL: + { + /* looks like control comes here with + * LWS_CALLBACK_HTTP_BIND_PROTOCOL, + * although based on docs would seem to expect + * LWS_CALLBACK_WS_SERVER_BIND_PROTOCOL + * + * In any case, control here when new websocket session created + */ + + if (!ws_pss) + break; + + assert(vhd); + + ws_pss->output_buf_ = new OutputBuffer(vhd->next_session_id_++); + + lwsl_user("establish pss->output_buf [%p] in ws_pss [%p]", + ws_pss->output_buf_, + ws_pss); + } + break; + case LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL: + { + if (!ws_pss) + break; + + lwsl_user("destroy pss->output_msg [%p] in ws_pss [%p]", + ws_pss->output_buf_, ws_pss); + + /* don't do this here. need to access ws_pss->output_buf + * from LWS_CALLBACK_CLOSED + */ +#ifdef BROKEN + if (ws_pss->output_buf_) { + delete ws_pss->output_buf_; + ws_pss->output_buf_ = nullptr; + } +#endif + } + break; + case LWS_CALLBACK_ESTABLISHED: + { + /* control comes here when a websocket session is opened + * (after protocol negotiated) + */ + + OutputBuffer * output_buf = ws_pss->output_buf_; + + output_buf->establish_wsi(wsi); + + websrv->notify_ws_session_open(output_buf, vhd, ws_token); + } + break; + + case LWS_CALLBACK_CLOSED: + { + /* control comes here when a websocket session is closed */ + assert(websrv); + assert(ws_pss); + assert(ws_pss->output_buf_); + assert(vhd); + + websrv->notify_ws_session_close(ws_pss->output_buf_, vhd, ws_token); + + if (ws_pss->output_buf_) { + delete ws_pss->output_buf_; + ws_pss->output_buf_ = nullptr; + } + + lwsl_user("LWS_CALLBACK_CLOSED: done"); + } + break; + + case LWS_CALLBACK_EVENT_WAIT_CANCELLED: + { + if (websrv) + websrv->lws_write_pending_traffic(ws_token); + } + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + { + /* control here when: + * 1. application wants to send data + * (app uses lws_cancel_service() to trigger + * LWS_CALLBACK_EVENT_WAIT_CANCELLED) + * 2. websocket session that was previously blocked + * is now ready to receive data + * (see LWS_CALLBACK_SERVER_WRITEABLE above) + */ + +#ifdef NOT_USING + if (!vhd->amsg.payload) + break; +#endif + + if (!vhd) { + lwsl_user("client entry: vhd not yet established, return"); + break; + } + + if (!ws_pss) { + lwsl_user("client entry: ws_pss not yet established, return"); + break; + } + + if (!(ws_pss->output_buf_)) { + lwsl_user("client entry: output_msg buffer not established, return"); + /* output message container hasn't been established, + * probably bc nothing to send + */ + break; + } + + lwsl_user("notify_minimal: unblock writing, output_msg=[%p]", + ws_pss->output_buf_); + + if (ws_pss->output_buf_->is_writeable(ws_token)) { + //assert(false); + } else { + /* a previous call to lws_write() reported a partial write; + * that write has now completed + */ + ws_pss->output_buf_->lws_write_completion(ws_token); + break; + } + + if (ws_pss->output_buf_->is_idle()) { + lwsl_user("client entry: output_msg buffer up-to-date, return"); + /* already up-to-date, nothing new to send */ + break; + } + +#ifdef NOT_USING + if (ws_pss->last == vhd->current) { + /* already up-to-date */ + break; + } + + if (!pss->output_msg_.payload) { + pss->output_msg_.payload = ::malloc(LWS_PRE + output_z); + pss->output_msg_.len = output_z; + } + + ::memcpy((char *)pss->output_msg_.payload + LWS_PRE, output_cstr, output_z); + + lwsl_user("allocate pss->output_msg [%p] in pss [%p]", + pss->output_msg_.payload, pss); + m = lws_write(wsi, + ((unsigned char *)pss->output_msg_.payload) + LWS_PRE, + pss->output_msg_.len, + LWS_WRITE_TEXT); +#endif + + //XO_SCOPE(lscope); + + /* pss->output_msg_ was populated from WebserverImpl.send_text(), q.v. */ + + m = ws_pss->output_buf_->lws_write_aux(ws_token); + +#ifdef NOT_USING + m = lws_write(wsi, ((unsigned char *)vhd->amsg.payload) + + LWS_PRE, vhd->amsg.len, LWS_WRITE_TEXT); + if (m < (int)vhd->amsg.len) { .. } +#endif + if (m == -1) { + lwsl_err("WebserverImplWsThread::notify_minimal: return -1 from callback"); + return -1; + } + + //pss->last = vhd->current; + } + break; + + case LWS_CALLBACK_RECEIVE: + { + char const * incoming_cmd + = reinterpret_cast(input); + + std::string_view incoming_svw(incoming_cmd, input_z); + + //lwsl_user("receive: [%s], z [%d]", incoming_cmd, (int)input_z); + + assert(ws_pss); + assert(websrv); + + uint32_t session_id = ws_pss->output_buf_->session_id(); + + websrv->perform_ws_cmd(session_id, + incoming_svw); + +#ifdef OBSOLETE + if (vhd->amsg.payload) + minimal_destroy_message(&(vhd->amsg)); + + vhd->amsg.len = input_z; //output_z; + /* notice we over-allocate by LWS_PRE */ + vhd->amsg.payload = ::malloc(LWS_PRE + input_z); + if (!vhd->amsg.payload) { + lwsl_user("OOM: dropping\n"); + break; + } + + ::memcpy((char *)vhd->amsg.payload + LWS_PRE, input, input_z); + //vhd->current++; +#endif + +#ifdef OBSOLETE + /* + * let everybody know we want to write something on them + * as soon as they are ready + */ + lws_start_foreach_llp(struct per_session_data__minimal **, + ppss, vhd->pss_list) { + lws_callback_on_writable((*ppss)->wsi); + } lws_end_foreach_llp(ppss, pss_list); +#endif + } + break; + + default: + break; + } + + log.end_scope(); + + return 0; + } /*notify_minimal*/ + + void + WebserverImplWsThread::run() + { + scope log(XO_DEBUG(false /*debug_flag*/)); + + lwsl_user("LWS minimal http server dynamic" + " | visit http://localhost:%d\n", this->ws_config_.port()); +#if defined(LWS_WITH_PLUGINS) + lwsl_user("LWS_WITH_PLUGINS present"); + lwsl_user("LWS_WITH_TLS present"); +#endif + + /* exit when .state is stop_requested, setting state to .stopped */ + + this->lws_cx_ = lws_create_context(&(this->cx_config_)); + + if (!(this->lws_cx_)) { + lwsl_err("lws init failed\n"); + return; + } + + std::int32_t n_event = 0; + while ((n_event >= 0) && !(this->interrupt_flag_)) { + n_event = ::lws_service(this->lws_cx_, + 0 /*ignored (used to be timeout)*/); + } + + log && log("webserver runner returned - service loop exited", + xtag("n_event", n_event), + xtag("interrupted", this->interrupt_flag_.load())); + + lws_context_destroy(this->lws_cx_); + this->lws_cx_ = nullptr; + + { + std::unique_lock lock(this->mutex_); + + this->state_ = Runstate::stopped; + this->cond_.notify_all(); + } + + log && log("exit"); + } /*run*/ + + rp + WebserverImplWsThread::make(WebserverConfig const & ws_config, + rp const & pjson) + { + return new WebserverImplWsThread(ws_config, pjson); + } /*make*/ + + // ----- Webserver ----- + + rp + Webserver::make(WebserverConfig const & ws_config, + rp const & pjson) { + return WebserverImplWsThread::make(ws_config, pjson); + } /*make*/ + + void + Webserver::display(std::ostream & os) const { + os << "state()) + << ">"; + } /*display*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end Webserver.cpp */ diff --git a/src/websock/WebsockUtil.cpp b/src/websock/WebsockUtil.cpp new file mode 100644 index 00000000..7c7057fb --- /dev/null +++ b/src/websock/WebsockUtil.cpp @@ -0,0 +1,141 @@ +/* @file WebsockUtil.cpp */ + +#include "WebsockUtil.hpp" + +#define STRINGIFY(x) #x + +namespace xo { + namespace web { + char const * + WebsockUtil::ws_callback_reason_descr(lws_callback_reasons x) { + +#define CASE(x) case x: return STRINGIFY(x) + + switch (x) { + CASE(LWS_CALLBACK_PROTOCOL_INIT); + CASE(LWS_CALLBACK_PROTOCOL_DESTROY); + CASE(LWS_CALLBACK_WSI_CREATE); + CASE(LWS_CALLBACK_WSI_DESTROY); + CASE(LWS_CALLBACK_WSI_TX_CREDIT_GET); + CASE(LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS); + CASE(LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS); + CASE(LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION); + CASE(LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY); + CASE(LWS_CALLBACK_SSL_INFO); + CASE(LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION); + CASE(LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED); + CASE(LWS_CALLBACK_HTTP); + CASE(LWS_CALLBACK_HTTP_BODY); + CASE(LWS_CALLBACK_HTTP_BODY_COMPLETION); + CASE(LWS_CALLBACK_HTTP_FILE_COMPLETION); + CASE(LWS_CALLBACK_HTTP_WRITEABLE); + CASE(LWS_CALLBACK_CLOSED_HTTP); + CASE(LWS_CALLBACK_FILTER_HTTP_CONNECTION); + CASE(LWS_CALLBACK_ADD_HEADERS); + CASE(LWS_CALLBACK_VERIFY_BASIC_AUTHORIZATION); + CASE(LWS_CALLBACK_CHECK_ACCESS_RIGHTS); + CASE(LWS_CALLBACK_PROCESS_HTML); + CASE(LWS_CALLBACK_HTTP_BIND_PROTOCOL); + CASE(LWS_CALLBACK_HTTP_DROP_PROTOCOL); + CASE(LWS_CALLBACK_HTTP_CONFIRM_UPGRADE); + CASE(LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP); + CASE(LWS_CALLBACK_CLOSED_CLIENT_HTTP); + CASE(LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ); + CASE(LWS_CALLBACK_RECEIVE_CLIENT_HTTP); + CASE(LWS_CALLBACK_COMPLETED_CLIENT_HTTP); + CASE(LWS_CALLBACK_CLIENT_HTTP_WRITEABLE); + CASE(LWS_CALLBACK_CLIENT_HTTP_REDIRECT); + CASE(LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL); + CASE(LWS_CALLBACK_CLIENT_HTTP_DROP_PROTOCOL); + CASE(LWS_CALLBACK_ESTABLISHED); + CASE(LWS_CALLBACK_CLOSED); + CASE(LWS_CALLBACK_SERVER_WRITEABLE); + CASE(LWS_CALLBACK_RECEIVE); + CASE(LWS_CALLBACK_RECEIVE_PONG); + CASE(LWS_CALLBACK_WS_PEER_INITIATED_CLOSE); + CASE(LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION); + CASE(LWS_CALLBACK_CONFIRM_EXTENSION_OKAY); + CASE(LWS_CALLBACK_WS_SERVER_BIND_PROTOCOL); + CASE(LWS_CALLBACK_WS_SERVER_DROP_PROTOCOL); + CASE(LWS_CALLBACK_CLIENT_CONNECTION_ERROR); + CASE(LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH); + CASE(LWS_CALLBACK_CLIENT_ESTABLISHED); + CASE(LWS_CALLBACK_CLIENT_CLOSED); + CASE(LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER); + CASE(LWS_CALLBACK_CLIENT_RECEIVE); + CASE(LWS_CALLBACK_CLIENT_RECEIVE_PONG); + CASE(LWS_CALLBACK_CLIENT_WRITEABLE); + CASE(LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED); + CASE(LWS_CALLBACK_WS_EXT_DEFAULTS); + CASE(LWS_CALLBACK_FILTER_NETWORK_CONNECTION); + CASE(LWS_CALLBACK_WS_CLIENT_BIND_PROTOCOL); + CASE(LWS_CALLBACK_WS_CLIENT_DROP_PROTOCOL); + CASE(LWS_CALLBACK_GET_THREAD_ID); + CASE(LWS_CALLBACK_ADD_POLL_FD); + CASE(LWS_CALLBACK_DEL_POLL_FD); + CASE(LWS_CALLBACK_CHANGE_MODE_POLL_FD); + CASE(LWS_CALLBACK_LOCK_POLL); + CASE(LWS_CALLBACK_UNLOCK_POLL); + CASE(LWS_CALLBACK_CGI); + CASE(LWS_CALLBACK_CGI_TERMINATED); + CASE(LWS_CALLBACK_CGI_STDIN_DATA); + CASE(LWS_CALLBACK_CGI_STDIN_COMPLETED); + CASE(LWS_CALLBACK_CGI_PROCESS_ATTACH); + CASE(LWS_CALLBACK_SESSION_INFO); + CASE(LWS_CALLBACK_GS_EVENT); + CASE(LWS_CALLBACK_HTTP_PMO); + CASE(LWS_CALLBACK_RAW_PROXY_CLI_RX); + CASE(LWS_CALLBACK_RAW_PROXY_SRV_RX); + CASE(LWS_CALLBACK_RAW_PROXY_CLI_CLOSE); + CASE(LWS_CALLBACK_RAW_PROXY_SRV_CLOSE); + CASE(LWS_CALLBACK_RAW_PROXY_CLI_WRITEABLE); + CASE(LWS_CALLBACK_RAW_PROXY_SRV_WRITEABLE); + CASE(LWS_CALLBACK_RAW_PROXY_CLI_ADOPT); + CASE(LWS_CALLBACK_RAW_PROXY_SRV_ADOPT); + CASE(LWS_CALLBACK_RAW_PROXY_CLI_BIND_PROTOCOL); + CASE(LWS_CALLBACK_RAW_PROXY_SRV_BIND_PROTOCOL); + CASE(LWS_CALLBACK_RAW_PROXY_CLI_DROP_PROTOCOL); + CASE(LWS_CALLBACK_RAW_PROXY_SRV_DROP_PROTOCOL); + CASE(LWS_CALLBACK_RAW_RX); + CASE(LWS_CALLBACK_RAW_CLOSE); + CASE(LWS_CALLBACK_RAW_WRITEABLE); + CASE(LWS_CALLBACK_RAW_ADOPT); + CASE(LWS_CALLBACK_RAW_CONNECTED); + CASE(LWS_CALLBACK_RAW_SKT_BIND_PROTOCOL); + CASE(LWS_CALLBACK_RAW_SKT_DROP_PROTOCOL); + CASE(LWS_CALLBACK_RAW_ADOPT_FILE); + CASE(LWS_CALLBACK_RAW_RX_FILE); + CASE(LWS_CALLBACK_RAW_WRITEABLE_FILE); + CASE(LWS_CALLBACK_RAW_CLOSE_FILE); + CASE(LWS_CALLBACK_RAW_FILE_BIND_PROTOCOL); + CASE(LWS_CALLBACK_RAW_FILE_DROP_PROTOCOL); + CASE(LWS_CALLBACK_TIMER); + CASE(LWS_CALLBACK_EVENT_WAIT_CANCELLED); + CASE(LWS_CALLBACK_CHILD_CLOSING); + CASE(LWS_CALLBACK_CONNECTING); + CASE(LWS_CALLBACK_VHOST_CERT_AGING); + CASE(LWS_CALLBACK_VHOST_CERT_UPDATE); + CASE(LWS_CALLBACK_MQTT_NEW_CLIENT_INSTANTIATED); + CASE(LWS_CALLBACK_MQTT_IDLE); + CASE(LWS_CALLBACK_MQTT_CLIENT_ESTABLISHED); + CASE(LWS_CALLBACK_MQTT_SUBSCRIBED); + CASE(LWS_CALLBACK_MQTT_CLIENT_WRITEABLE); + CASE(LWS_CALLBACK_MQTT_CLIENT_RX); + CASE(LWS_CALLBACK_MQTT_UNSUBSCRIBED); + CASE(LWS_CALLBACK_MQTT_DROP_PROTOCOL); + CASE(LWS_CALLBACK_MQTT_CLIENT_CLOSED); + CASE(LWS_CALLBACK_MQTT_ACK); + CASE(LWS_CALLBACK_MQTT_RESEND); + CASE(LWS_CALLBACK_MQTT_UNSUBSCRIBE_TIMEOUT); + CASE(LWS_CALLBACK_MQTT_SHADOW_TIMEOUT); + CASE(LWS_CALLBACK_USER); + } + +#undef CASE + + return "???"; + } /*ws_callback_reason_descr*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end WebsockUtil.cpp */ diff --git a/src/websock/WebsocketSink.cpp b/src/websock/WebsocketSink.cpp new file mode 100644 index 00000000..1c406432 --- /dev/null +++ b/src/websock/WebsocketSink.cpp @@ -0,0 +1,148 @@ +/* file WebsocketSink.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "WebsocketSink.hpp" +#include "Webserver.hpp" +#include "xo/printjson/PrintJson.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/reflect/TaggedPtr.hpp" +#include "xo/indentlog/scope.hpp" + +namespace xo { + using xo::reactor::AbstractSource; + using xo::json::PrintJson; + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::TypeDescr; + using xo::ref::rp; + using xo::ref::brw; + using xo::print::quoted; + using xo::print::qcstr; + using xo::scope; + using xo::xtag; + + namespace web { + /* a sink that publishes to a websocket. + * The websocket api creates a WebsocketSink instance + * on behalf of an incoming subscription request. + * application code will hold onto the sink somewhere + * and publish events to it, to send them via websocket. + */ + class WebsocketSinkImpl : public WebsocketSink { + public: + using PrintJson = xo::json::PrintJson; + using AbstractSource = reactor::AbstractSource; + + public: + WebsocketSinkImpl(ref::rp const & websrv, + ref::rp const & pjson, + uint32_t session_id, + std::string stream_name) + : websrv_{std::move(websrv)}, + pjson_{std::move(pjson)}, + session_id_{session_id}, + stream_name_{std::move(stream_name)} + {} + + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { this->name_ = x; } + /* 0 consumers for websocket sink, since it's not a source */ + virtual void visit_direct_consumers(std::function)> const &) override {} + virtual void display(std::ostream & os) const override; + + virtual bool allow_polymorphic_source() const override { return true; } + virtual TypeDescr sink_ev_type() const override; + virtual bool allow_volatile_source() const override { return true; } + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + virtual void attach_source(ref::rp const & src) override; + virtual void notify_ev_tp(TaggedPtr const & ev_tp) override; + + private: + /* (ideally unique) user-controlled name for this sink + * in practice not likely to be accessible, + * so probably want to generate a unique-y default + */ + std::string name_; + /* webserver implementation */ + ref::rp websrv_; + /* print arbitrary reflected stuff as json */ + ref::rp pjson_; + /* websocket session id# - events arriving at this sink + * will be sent only to the session identified by .session_id + */ + uint32_t session_id_; + /* name for stream. + * this will be the vale of the "stream" tag in + * initiating subscription message + * {"cmd": "subscribe", "stream", "/this/stream/name"} + * e.g. in python: + * web.register_stream_endpoint(kf.stream_endpoint_descr("/this/stream/name")) + */ + std::string stream_name_; + /* count #of events received */ + uint32_t n_in_ev_ = 0; + }; /*WebsocketSinkImpl*/ + + TypeDescr + WebsocketSinkImpl::sink_ev_type() const + { + return Reflect::require(); + } /*sink_ev_type*/ + + void + WebsocketSinkImpl::attach_source(rp const & src) { + src->attach_sink(this); + } /*attach_source*/ + + void + WebsocketSinkImpl::notify_ev_tp(TaggedPtr const & ev_tp) + { + scope log(XO_DEBUG(true /*debug_flag*/)); + + std::stringstream ss; + + /* format message envelope */ + ss << "{" << qcstr("stream") << ": " << quoted(this->stream_name_) + << ", " << qcstr("event") << ": "; + + /* format event as json */ + this->pjson_->print_tp(ev_tp, &ss); + + ss << "}"; + + log && log("sending", xtag("msg", ss.str())); + + ++(this->n_in_ev_); + + /* send event via associated websocket */ + this->websrv_->send_text(this->session_id_, ss.str()); + + } /*notify_ev_tp*/ + + void + WebsocketSinkImpl::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + // ----- WebsocketSink ----- + + rp + WebsocketSink::make(rp const & websrv, + rp const & pjson, + uint32_t session_id, + std::string const & stream_name) + { + return new WebsocketSinkImpl(websrv, pjson, session_id, stream_name); + } /*make*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end WebsocketSink.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..c8fcdacc --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,47 @@ +# build unittest websock/utest + +set(SELF_EXE utest.websock) +set(SELF_SRCS websock_utest_main.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +## note: can't add this yet, because test not automated. +## requires manual interaction from browser +## +#add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +#target_code_coverage(${SELF_EXE} AUTO ALL) + +# copy static {.html, .js, .svg} files to build directory +file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/mount-origin/" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/mount-origin") + +# ---------------------------------------------------------------- +# internal dependency (on this codebase) + +xo_self_dependency(${SELF_EXE} websock) + +# ---------------------------------------------------------------- +# external dependencies + +target_link_libraries(${SELF_EXE} PUBLIC websock) +# Need to port option, volfit before we can build this test here +target_link_libraries(${SELF_EXE} PUBLIC option) +target_link_libraries(${SELF_EXE} PUBLIC volfit) +#target_link_libraries(utest.option PUBLIC logutil) + + +# should be getting this via xo_include_options2() + +## ---------------------------------------------------------------- +## make standard directories for std:: includes explicit +## so that +## (1) they appear in compile_commands.json. +## (2) clangd (run from emacs lsp-mode) can find them +## +#if(CMAKE_EXPORT_COMPILE_COMMANDS) +# set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES +# ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +#endif() + +# end CMakeLists.txt diff --git a/utest/README b/utest/README new file mode 100644 index 00000000..8ed18777 --- /dev/null +++ b/utest/README @@ -0,0 +1,13 @@ +To run this unit test: + + $ cd path/to/kalman/build/src/websock/utest + $ ./utest.websock # listens for http requests on port 7682 + +point browser to + + localhost:7682/ex_websock.html + +static files served from + + path/to/kalman/build/src/websock/utest/mount-origin + \ No newline at end of file diff --git a/utest/mount-origin/bluecircle.svg b/utest/mount-origin/bluecircle.svg new file mode 100644 index 00000000..8c7b3296 --- /dev/null +++ b/utest/mount-origin/bluecircle.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/utest/mount-origin/d3ex/d3ex.ch5.ex1.html b/utest/mount-origin/d3ex/d3ex.ch5.ex1.html new file mode 100644 index 00000000..7b3c62d4 --- /dev/null +++ b/utest/mount-origin/d3ex/d3ex.ch5.ex1.html @@ -0,0 +1,15 @@ + + + + + + + simple d3 example + + + diff --git a/utest/mount-origin/ex_websock.html b/utest/mount-origin/ex_websock.html new file mode 100644 index 00000000..53963e20 --- /dev/null +++ b/utest/mount-origin/ex_websock.html @@ -0,0 +1,13 @@ + + + + pywebsock example page + + + +

pywebsock example page

+ +
+
+ + diff --git a/utest/mount-origin/ex_websock.js b/utest/mount-origin/ex_websock.js new file mode 100644 index 00000000..c26406ba --- /dev/null +++ b/utest/mount-origin/ex_websock.js @@ -0,0 +1,842 @@ +/* webpage to display kalman filter output + * coordinates with ex_websock.py in this directory + */ + +import * as d3 from "https://cdn.skypack.dev/d3@7"; +/* json5 accepts ieee floatingpoint special values; + * regular json excludes them (!?#) + */ +import JSON5 from "https://unpkg.com/json5@2/dist/index.min.mjs"; + +/* NOTE: put "export" in front of a variable/function + * that we want to make accessible outside this module + */ + +/* for use for browser's javascript console */ +globalThis.d3 = d3; +//globalThis.jparse = JSON5.parse; + +/* u: document.URL */ +function choose_ws_url(suffix_url) +{ + var pcol; + var u = document.URL; + + /* + * We open the websocket encrypted if this page came on an + * https:// url itself, otherwise unencrypted + */ + + if (u.substring(0, 5) === "https") { + pcol = "wss://"; + u = u.substr(8); + } else { + pcol = "ws://"; + if (u.substring(0, 4) === "http") + u = u.substr(7); + } + + u = u.split("/"); + + /* + "/xxx" bit is for IE10 workaround */ + + return pcol + u[0] + "/" + suffix_url; +} /*choose_ws_url*/ + +class Datatype { + #typename = null; + #nominal = null; + /* .from_json(x) convert a value received in json format + * to native representation. + */ + #from_json = null; + /* .make_scale(range) builds d3 scale object */ + #make_scale = null; + + constructor(typename, nominal, from_json, make_scale) { + this.#typename = typename; + this.#nominal = nominal; + this.#from_json = from_json; + this.#make_scale = make_scale; + } + + typename() { return this.#typename; } + nominal() { return this.#nominal; } + from_json(x) { return this.#from_json(x); } + make_scale(domain) { return this.#make_scale(domain); } +}; /*Datatype*/ + +class DatatypeFactory { + static dtype_map = DatatypeFactory.make_dtype_map(); + + static make_float_dtype() { + return new Datatype("float" /*typename*/, + 0.0 /*nominal*/, + (x) => { return x; } /*from_json*/, + (dom) => { return d3.scaleLinear().domain(dom); } /*make_scale*/ + ); } + + static make_datetime_dtype() { + return new Datatype("datetime" /*typename*/, + new Date() /*nominal*/, + (x) => { return new Date(x); } /*from_json*/, + (dom) => { return d3.scaleTime().domain(dom); } /*make_scale*/ + ); } + + static make_dtype_map() { + let retval = new Map(); + + retval.set("float", DatatypeFactory.make_float_dtype()); + retval.set("datetime", DatatypeFactory.make_datetime_dtype()); + + return retval; + } + + static lookup(typename) { + if (DatatypeFactory.dtype_map.has(typename)) { + return DatatypeFactory.dtype_map.get(typename); + } else { + throw new Error("DatatypeFactory: typename [" + + typename + + "] found where float|datetime expected"); + } + } +}; /*DatatypeFactory*/ + +/* class to extract event values for charting. + * 'traits' because applies to separately-represented event objects + */ +class DataTraits { + /* .x_slotlookup(ev) => x-value */ + #x_slotlookup = null; + /* .y_slotlookup(ev) => y-value */ + #y_slotlookup = null; + #x_datatype = null; //DatatypeFactory.lookup("datetime"); + #y_datatype = null; //DatatypeFactory.lookup("float"); + + /* x_nt, y_nt: each should be a pair [slotlookup, typename] + * - slotname is a function :: event -> jsonvalue, + * that extracts an attribute from incoming event in json format + * - typename is float|datetime + */ + constructor(x_nt, y_nt) { + this.#x_slotlookup = x_nt[0]; + this.#x_datatype = DatatypeFactory.lookup(x_nt[1]); + this.#y_slotlookup = y_nt[0]; + this.#y_datatype = DatatypeFactory.lookup(y_nt[1]); + } + + x_datatype() { return this.#x_datatype; } + y_datatype() { return this.#y_datatype; } + + x_nominal() { return this.#x_datatype.nominal(); } + y_nominal() { return this.#y_datatype.nominal(); } + + mapkey(data_ev) { return this.#x_slotlookup(data_ev); } + x_value(data_ev) { return this.#x_datatype.from_json(this.#x_slotlookup(data_ev)); } + y_value(data_ev) { return this.#y_datatype.from_json(this.#y_slotlookup(data_ev)); } + + make_x_scale(domain) { return this.#x_datatype.make_scale(domain); } + make_y_scale(domain) { return this.#y_datatype.make_scale(domain); } +}; /*DataTraits*/ + +function range_outer(lh, rh) { + return [Math.min(lh[0], rh[0]), + Math.max(lh[1], rh[1])]; +} /*range_outer*/ + +/* a dataset driving a chart. + * + * PLAN: multiple lines in the same chart + * - makeitso dataset can contain multiple data series + * - give each series within a dataset its own index# + * - each series computes its own min/max x/y values + * - take union across series to get chart x/y range + * - new class Dataset + */ +class Dataseries { + /* normalizing transformation for event objects. + * use to produce events {.x_value, .y_value} + key suitable for Map + */ + #data_traits = null; //new DataTraits(); + /* .dataset_map :: string -> {key value pair} + * must use string as keys, since Map uses object identity if key is Object + */ + #dataset_map = new Map(); + /* vector of key-value pairs, in increasing x-axis order */ + #dataset_v = []; + /* min,max value of dataset[i].x_value */ + #dset_min_x = null; + #dset_max_x = null; + /* min,max value of dataset[i].y_value */ + #dset_min_y = null; + #dset_max_y = null; + + #max_key = 0; + + constructor(data_traits) { + this.#data_traits = data_traits; + this.recalc_minmax(); + } + + data_traits() { return this.#data_traits; } + dataset_v() { return this.#dataset_v; } + x_range() { return [this.#dset_min_x, this.#dset_max_x]; } + y_range() { return [this.#dset_min_y, this.#dset_max_y]; } + + /* data_ev must have attributes consistent with what .#data_traits expects */ + update_dataset(data_ev) { + //console.log("Dataseries.update_dataset: data_ev=", data_ev); + + let x = this.#data_traits.x_value(data_ev); + let y = this.#data_traits.y_value(data_ev); + /* using this key to recognize + suppress duplicate points + * (e.g. if browser winds up sending multiple snapshot requests + * for the same dataset) + */ + let mapkey = this.#data_traits.mapkey(data_ev); + + //console.log("Dataseries.update_dataset: x=", x, ", y=", y, ", mapkey=", mapkey); + + /* in map must use time strings (not Dates) as keys */ + if (this.#dataset_map.has(mapkey)) { + /*skip -- assuming that source is immutable */; + } else { + /* kv.key is ordinal number identifying a datum. + * not related to mapkey, except in so far as both work as datum ids + */ + let kv = {key: this.#max_key, + x_value: x, + y_value: y}; + + /* (reminder: js map keys need to be strings) */ + this.#dataset_map.set(mapkey, kv); + this.#dataset_v.push(kv); + this.#max_key = this.#max_key+1; + } + } /*update_dataset*/ + + recalc_minmax() { + if (this.#dataset_v.length == 0) { + /* min,max value of dataset[i].x_value */ + this.#dset_min_x = this.#data_traits.x_nominal(); + this.#dset_max_x = this.#data_traits.x_nominal(); + /* min,max value of dataset[i].y_value */ + this.#dset_min_y = this.#data_traits.y_nominal(); + this.#dset_max_y = this.#data_traits.y_nominal(); + } else { + /* min,max value of dataset[i].x_value */ + this.#dset_min_x = d3.min(this.#dataset_v, (d) => { return d.x_value; }); + this.#dset_max_x = d3.max(this.#dataset_v, (d) => { return d.x_value; }); + /* min,max value of dataset[i].y_value */ + this.#dset_min_y = d3.min(this.#dataset_v, (d) => { return d.y_value; }); + this.#dset_max_y = d3.max(this.#dataset_v, (d) => { return d.y_value; }); + } + } /*recalc_minmax*/ + + /* note: caller should invoke .range() before using for drawing */ + make_x_scale(xrange) { + return this.#data_traits.make_x_scale(xrange /*domain*/); + } /*make_x_scale*/ + + /* note: caller should invoke .range() before using for drawing */ + make_y_scale(yrange) { + return this.#data_traits.make_y_scale(yrange /*domain*/); + } /*make_y_scale*/ +}; /*Dataseries*/ + +/* bundle multiple dataseries for charting + * for now: can have multiple series, but they need to be driven + * from the same native row storage + */ +class Dataset { + #dataseries_v = []; + /* min/max x-values across all members of .dataseries_v */ + #outer_x_range = null; + /* min/max y-values across all members of .dataseries_v */ + #outer_y_range = null; + + constructor(data_traits_v) { + for (let i=0, n=data_traits_v.length; i tag above in DOM sketch)*/ + #chart_svg = null; + + constructor(w, h, pad) { + this.#chart_w = w; + this.#chart_h = h; + this.#chart_pad = pad; + } + + x_range() { return [this.#chart_pad, + this.#chart_w - this.#chart_pad]; } + /* note: inverting bc svg y-values increase towards bottom of screen; + * we want y-values to increase towards top of screen + */ + y_range() { return [this.#chart_h - this.#chart_pad, + this.#chart_pad]; } + + require_gui(parent_d3sel, dataset) { + this.#require_x_scale(dataset); + this.#require_y_scale(dataset); + this.#require_linegen(); /*will use .chart_x_scale, .chart_y_scale */ + this.#require_x_axis(); /*will use .chart_x_scale*/ + this.#require_y_axis(); /*will use .chart_y_scale*/ + this.#require_svg(parent_d3sel, dataset); + } + + /* dom element id to use for the i'th dataseries in this chart */ + series_html_id(i_dataseries) { + return "pts-" + i_dataseries; + } + + /* update chart for new dataset contents + * + * Require: + * - .require_gui(_, dataset) has been called + * - #of dataseries has not changed since last call to .require_svg() + */ + update_chart(dataset) { + /* update d3 scales + * (shared across all series bundled into this dataset + */ + this.#rescale_chart(dataset); + + for (let i=0, n=dataset.n_dataseries(); i { return this.#chart_x_scale(d.x_value); }) + .y((d) => { return this.#chart_y_scale(d.y_value); })); + } + } /*require_linegen*/ + + #require_x_axis() { + if (!this.#chart_x_axis) { + this.#chart_x_axis = (d3 + .axisBottom() + .scale(this.#chart_x_scale) + .ticks(10)); + } + } + + #require_y_axis() { + if (!this.#chart_y_axis) { + this.#chart_y_axis = (d3 + .axisLeft() + .scale(this.#chart_y_scale) + .ticks(10)); + } + } + + #require_svg(parent_d3sel, dataset) { + if (!this.#chart_svg) { + this.#chart_svg = (parent_d3sel // .select("#uls") + .append("svg") + .attr("width", this.#chart_w) + .attr("height", this.#chart_h)); + + /* svg group comprising x-axis */ + this.#chart_svg.append("g") + .attr("class", "xaxis") + .attr("id", "x_axis") + .attr("transform", this.#x_axis_translate_str()) + .call(this.#chart_x_axis); + + /* svg group comprising y-axis */ + this.#chart_svg.append("g") + .attr("class", "yaxis") + .attr("id", "y_axis") + .attr("transform", this.#y_axis_translate_str()) + .call(this.#chart_y_axis); + + for (let i=0, n=dataset.n_dataseries(); i (will attach svg element here) + * +- + * +- (d3 will draw x-axis inside, .chart_x_axis() draws) + * +- (d3 will draw y-axis inside, .chart_y_axis() draws) + * +- + * +- (.chart_line_gen draws) + */ +class TimeseriesCtl extends Controller { + #dataset_uri = ''; + #dataset = null; + + #chart = new LineChart(500 /*w*/, + 250 /*h*/, + 50 /*pad*/); + + constructor(dataset_uri, data_traits_v) { + super(); + this.#dataset_uri = dataset_uri; + this.#dataset = new Dataset(data_traits_v); + } + + static rescale_dataset(dataset) { + dataset.recalc_minmax(); + } /*rescale_dataset*/ + + /* request dataseries snapshot from webserver; + * update+draw graph when snapshot arrives + * + * NOTE: + * 1. typical web docs (e.g. MDN) will advise using response.json(): + * fetch(uri) + * .then((response) => response.json()) + * .then((data) => dostuffwith(data)) + * + * however, this has a flaw: standard json is missing special floating-point values (!!); + * in particular it has no representation for nan/+inf/-inf + * 2. we want to use the extended json standard 'json5'; + * however need care since JSON5.parse() fails spuriously (at least JSON5/chrome asof 24sep2022) + * if given a promise + */ + request() { + fetch(this.#dataset_uri) + .then((response) => response.text()) + .then((text) => this.on_snapshot_text(text)); + +// .then((text) => JSON5.parse() +// .then((data) => this.on_snapshot(data)); + } /*request*/ + + /* update from snapshot json text */ + on_snapshot_text(text) { + const data = JSON5.parse(text); + + this.on_snapshot(data); + } /*on_snapshot_text*/ + + /* update from snapshot + * + * .on_snapshot() => .#dataset => .on_dataset() + */ + on_snapshot(data) { + //console.log("on_snapshot: data=", data); + + data.forEach((x, i) => { + // REFACTORME + + if (x._name_ == "UpxEvent") { + this.on_update(x); + } else if (x._name_ == "KalmanFilterStateExt") { + this.on_update(x); + } else { + console.log("unexpected json record x=", x); + } + }); + + this.on_dataset(this.#dataset); + } /*on_snapshot*/ + + /* update from websocket + * + * .on_update() => .#dataset => .on_dataset() + */ + on_update(data_ev) { + this.#dataset.update_dataset(data_ev); + + this.on_dataset(this.#dataset); + } /*on_update*/ + + /* call after modifying .#dataset + * + * .on_dataset() =|=> .rescale_dataset() =|========> .chart_x_axis ===\ + * | |========> .chart_x_axis =\ | + * | | | + * |=> .chart_svg.#x_axis <==========================/ | + * |=> .chart_svg.#y_axis <============================/ + */ + on_dataset(dataset) { + //console.log("on_dataset: dataset=", dataset); + + // update x-scale, y-scale + TimeseriesCtl.rescale_dataset(dataset); + + this.#chart.update_chart(dataset); + } /*on_dataset*/ + + /* e.g. + * ctl.require_gui(d3.select("#uls")) + * to build chart gui under DOM element with id="uls" + */ + require_gui(parent_d3sel) { + this.#chart.require_gui(parent_d3sel, this.#dataset); + } /*require_gui*/ + +}; /*TimeseriesCtl*/ + +/* controller for timeseries graph, from uri [/dyn/uls/snap] + [/ws/uls] */ +var uls_ctl = false; +var uls_ctl_enabled = true; + +if (uls_ctl_enabled) { + uls_ctl = new TimeseriesCtl('/dyn/uls/snap', + [new DataTraits([(ev) => ev.tm, "datetime"], + [(ev) => ev.upx, "float"])]); + + uls_ctl.require_gui(d3.select("#uls")); + uls_ctl.request(); +} + +/* controller for timeseries graph, from uri [/dyn/kfs/snap] + [/ws/kfs] */ +var kfs_ctl = false; +var kfs_ctl_enabled = true; + +if (kfs_ctl_enabled) { + kfs_ctl = new TimeseriesCtl('/dyn/kfs/snap', + [new DataTraits([(ev) => ev.tk, + "datetime"], + [(ev) => { return ev.x[0]; }, + "float"]), + /* 2.sigma below estimate */ + new DataTraits([(ev) => ev.tk, + "datetime"], + [(ev) => { return Math.max(0.0, ev.x[0] - 2.0 * Math.sqrt(ev.P[0][0])); }, + "float"]), + /* 2.sigma above estimate */ + new DataTraits([(ev) => ev.tk, + "datetime"], + [(ev) => { return Math.min(1.0, ev.x[0] + 2.0 * Math.sqrt(ev.P[0][0])); }, + "float"]) + ]); + + kfs_ctl.require_gui(d3.select("#kfs")); + kfs_ctl.request(); +} + +let key_fn = ((d) => { return d.key; }); + +/* controller for volsurface graph (strike -> volatility), + * from uri [/dyn/kf/snap] + */ + +d3.select("#refresh") + .on("click", + function() { + console.log("button[#refresh] clicked"); + + uls_ctl.request(); + }); + +var srv_ws = null; + +function content_loaded_fn() +{ + console.log("Hi Roly, DOM loaded"); + + /* use this url to create websocket to the server that delivered current webpage */ + let ws_url = choose_ws_url("" /*url_suffix*/); + console.log("ws_url: [", ws_url, "]"); + + srv_ws = new WebSocket(ws_url, "lws-minimal"); + + try { + // srv_ws.onopen = () => { ... }; + // srv_ws.onclose = () => { ... }; + srv_ws.onmessage = (msg) => { + /* msg has dozens of attributes, too many to list here + * actual application message appears in the .data attribute + * (as nested js string) + */ + //console.log("incoming ws msg: [", msg, "]"); + + let msgdata = JSON5.parse(msg.data); + + //console.log("msgdata: [", msgdata, "]"); + + let stream_name = msgdata.stream; + let event = msgdata.event; + + if (stream_name == "/ws/uls") { + if (uls_ctl) { + uls_ctl.on_update(event); + } + } else if (stream_name == "/ws/kfs") { + if (kfs_ctl) { + kfs_ctl.on_update(event); + } + } else { + console.log("unknown stream name [", stream_name, "]"); + } + }; + } catch(excetpion) { + asert("

Error: " + exception); + } + + console.log("srv_ws state [", srv_ws.readyState, "]"); + + srv_ws.addEventListener('open', + (event) => { + console.log("srv_ws state [", srv_ws.readyState, "]"); + if (uls_ctl) { + srv_ws.send("{\"cmd\": \"subscribe\", \"stream\": \"/ws/uls\"} "); + } + if (kfs_ctl) { + srv_ws.send("{\"cmd\": \"subscribe\", \"stream\": \"/ws/kfs\"} "); + } + //socket.send('Hello Server!'); + }); + +} /*content_loaded_fn*/ + +document.addEventListener("DOMContentLoaded", + content_loaded_fn, + false); + +/* end ex_websock.js */ diff --git a/utest/mount-origin/example.js b/utest/mount-origin/example.js new file mode 100644 index 00000000..4df6c234 --- /dev/null +++ b/utest/mount-origin/example.js @@ -0,0 +1,64 @@ +function get_appropriate_ws_url(extra_url) +{ + var pcol; + var u = document.URL; + + /* + * We open the websocket encrypted if this page came on an + * https:// url itself, otherwise unencrypted + */ + + if (u.substring(0, 5) === "https") { + pcol = "wss://"; + u = u.substr(8); + } else { + pcol = "ws://"; + if (u.substring(0, 4) === "http") + u = u.substr(7); + } + + u = u.split("/"); + + /* + "/xxx" bit is for IE10 workaround */ + + return pcol + u[0] + "/" + extra_url; +} + +function new_ws(urlpath, protocol) +{ + return new WebSocket(urlpath, protocol); +} + +document.addEventListener("DOMContentLoaded", function() { + + var ws = new_ws(get_appropriate_ws_url(""), "lws-minimal"); + try { + ws.onopen = function() { + document.getElementById("m").disabled = 0; + document.getElementById("b").disabled = 0; + }; + + ws.onmessage =function got_packet(msg) { + document.getElementById("r").value = + document.getElementById("r").value + msg.data + "\n"; + document.getElementById("r").scrollTop = + document.getElementById("r").scrollHeight; + }; + + ws.onclose = function(){ + document.getElementById("m").disabled = 1; + document.getElementById("b").disabled = 1; + }; + } catch(exception) { + alert("

Error " + exception); + } + + function sendmsg() + { + ws.send(document.getElementById("m").value); + document.getElementById("m").value = ""; + } + + document.getElementById("b").addEventListener("click", sendmsg); + +}, false); diff --git a/utest/mount-origin/index.html b/utest/mount-origin/index.html new file mode 100644 index 00000000..49738af1 --- /dev/null +++ b/utest/mount-origin/index.html @@ -0,0 +1,19 @@ + + + + + + + +
+ + LWS chat minimal ws server example.
+ Chat is sent to all browsers open on this page. +
+
+
+ + + + + diff --git a/utest/mount-origin/libwebsockets.org-logo.svg b/utest/mount-origin/libwebsockets.org-logo.svg new file mode 100644 index 00000000..ef241b37 --- /dev/null +++ b/utest/mount-origin/libwebsockets.org-logo.svg @@ -0,0 +1,66 @@ + + + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/utest/mount-origin/script-csp.svg b/utest/mount-origin/script-csp.svg new file mode 100644 index 00000000..cd128f1d --- /dev/null +++ b/utest/mount-origin/script-csp.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/utest/websock_utest_main.cpp b/utest/websock_utest_main.cpp new file mode 100644 index 00000000..fd2fd9aa --- /dev/null +++ b/utest/websock_utest_main.cpp @@ -0,0 +1,282 @@ +/* @file websock_utest_main.cpp */ + +#include "websock/Webserver.hpp" +#include "volfit/init_volfit.hpp" +#include "volfit/Volfit.hpp" +#include "volfit/VolfitInputCapture.hpp" +#include "filter/init_filter.hpp" +#include "filter/KalmanFilterSvc.hpp" +#include "option/StrikeSetOmd.hpp" +#include "option/StrikeSetMarketModel.hpp" +#include "option/UlMarketModel.hpp" +#include "option/PricingContext.hpp" +#include "option/OptionStrikeSet.hpp" +#include "process/init_process.hpp" +#include "process/RealizationSource.hpp" +#include "process/RealizationTracer.hpp" +#include "process/UpxEvent.hpp" +#include "process/ExpProcess.hpp" +#include "process/BrownianMotion.hpp" +#include "simulator/init_simulator.hpp" +#include "simulator/Simulator.hpp" +#include "reactor/EventStore.hpp" +#include "randomgen/random_seed.hpp" +#include "randomgen/xoshiro256.hpp" +#include "time/Time.hpp" +#include "printjson/PrintJson.hpp" +#include "indentlog/print/tag.hpp" +#include + +/* webserver instance */ +static xo::ref::rp g_ws; + +void sigint_handler(int /*sig*/) { + std::cerr << "main thread interrupt_handler\n"; + + if (g_ws) + g_ws->interrupt_stop_webserver(); +} + +int +main(int argc, char **argv) { + using xo::web::Webserver; + using xo::web::WebserverConfig; + using xo::json::PrintJsonSingleton; + using xo::vf::Volfit; + using xo::vf::VolfitInputCaptureSvc; + using xo::kalman::KalmanFilterStateExt; + using xo::kalman::KalmanFilterSvc; + using xo::kalman::KalmanFilterSpec; + using xo::option::FlatVolsfc; + using xo::option::BboTick; + using xo::option::StrikeSetOmd; + using xo::option::StrikeSetMarketModel; + using xo::option::UlMarketModel; + using xo::option::PricingContext; + using xo::option::OptionStrikeSet; + using xo::option::Secid; + using xo::option::Pxtick; + using xo::process::UpxEvent; + using xo::process::RealizationSource; + using xo::process::RealizationTracer; + using xo::process::StochasticProcess; + using xo::process::ExpProcess; + using xo::process::BrownianMotion; + using xo::sim::Simulator; + using xo::reactor::PtrEventStore; + using xo::reactor::StructEventStore; + using xo::reactor::ReactorSource; + using xo::rng::Seed; + using xo::rng::xoshiro256ss; + using xo::ref::rp; + using xo::time::utc_nanos; + using xo::time::timeutil; + using xo::json::PrintJsonSingleton; + using xo::json::PrintJson; + using xo::Subsystem; + using xo::scope; + using xo::xtag; + + try { +#ifdef NOT_USING + InitSubsys::require(); + InitSubsys::require(); + InitSubsys::require(); +#endif + + XO_SUBSYSTEM_REQUIRE(volfit); + XO_SUBSYSTEM_REQUIRE(filter); + XO_SUBSYSTEM_REQUIRE(simulator); + XO_SUBSYSTEM_REQUIRE(process); + + Subsystem::initialize_all(); + + signal(SIGINT, sigint_handler); + + scope log(XO_ENTER0(always)); + + rp pjson = PrintJsonSingleton::instance(); + + /* RC Sep 2022 - adding c++ translation of kalman/src/pywebsock/ex_websock.py; + * intending to debug server segfault without complication of running + * from python interpreter + */ + + Secid secid0(0, 0); + Secid ul0 = Secid::ul(0); + + utc_nanos t0 = timeutil::ymd_hms_usec(20220926 /*ymd*/, + 93000 /*hms*/, + 0 /*usec*/); + utc_nanos t1 = t0 + std::chrono::hours(30 * 24); + + /* sim = pysimulator.Simulator.make() */ + rp sim = Simulator::make(t0); + + /* ss = pyoption.make_option_strike_set(3, secid0, 10, 1, t1, Pxtick.penny_nickel) */ + rp ss + = OptionStrikeSet::regular(3 /*n*/, + secid0 /*start_id*/, + 10.0 /*lo_strike*/, + 1.0 /*d_strike*/, + t1 /*expiry*/, + Pxtick::penny_nickel); + + /* cx = pyoption.make_pricing_context(t0, 11.11, .5, .06) */ + rp cx + = PricingContext::make(t0, 11.11 /*ref_spot*/, + FlatVolsfc::make(0.5) /*volatility*/, + 0.06 /*rate*/); + + /* TODO: replace with constant to get deterministic behavior */ + Seed seed; + + /* ebm = pyprocess.make_exponential_brownian_motion(t0, 11.0, 0.5) */ + rp ebm + = ExpProcess::make(11.0, + BrownianMotion::make(t0, + 0.5 /*volatility*/, + seed)); + + /* src = pyprocess.make_realization_source(ebm, dt.timedelta(seconds=1)) */ + rp src + = RealizationSource::make(RealizationTracer::make(ebm), + std::chrono::seconds(1)); + src->set_name("src"); + + /* (A) + * ulm = pyoption.make_ul_market_model(ul0, cx) + */ + rp ulm = UlMarketModel::make(ul0, cx); + ulm->set_name("ulm"); + + /* (B) + * ssm = pyoption.make_strikeset_market_model(ss, cx) + */ + rp ssm = StrikeSetMarketModel::make(ss, cx); + ssm->set_name("ssm"); + + /* (C) + * ssmd = pyoption.make_strikeset_omd(ss) + */ + rp ssmd = StrikeSetOmd::make(ss); + ssmd->set_name("ssmd"); + + /* (D) + * bbos = pyoption.BboTickStore.make() + */ + using BboTickStore = StructEventStore; + rp bbos = StructEventStore::make(); + bbos->set_name("bbos"); + + /* (E) + * vfin = pyvolfit.make_volfit_input_capture(cx) + */ + rp vfin = VolfitInputCaptureSvc::make(cx); + vfin->set_name("vfin"); + + /* (F) + * kfspec = pyvolfit.make_kf_spec_m1(t0, s0=0.3, p0=1.0, q=5) + */ + KalmanFilterSpec kfspec = Volfit::kf_spec_m1(t0, 0.3, 1.0, 5.0); + /* kf = pyfilter.make_kalman_filter(spec=kfspec) */ + rp kf = KalmanFilterSvc::make(kfspec); + kf->set_debug_sim_flag(true); + kf->set_name("kf"); + + using KalmanFilterStateEventStore + = PtrEventStore>; + + /* (G) + * kfs = pyfilter.KalmanFilterStateEventStore.make() + */ + rp kfs + = KalmanFilterStateEventStore::make(); + + /* sim.add_source(src) */ + sim->add_source(src); + + /* uls = pyprocess.UpxEventStore.make() */ + using UpxEventStore = StructEventStore; + rp uls = UpxEventStore::make(); + src->attach_sink(uls); + + /* (A) */ + sim->add_source(ulm); + src->attach_sink(ulm); + + /* (B) */ + sim->add_source(ssm); + src->attach_sink(ssm); + + /* (C) */ + sim->add_source(ssmd); + ulm->attach_sink(ssmd); + ssm->attach_sink(ssmd); + + /* (D) */ + ulm->attach_sink(bbos); + ssm->attach_sink(bbos); + + /* (E) */ + // sim->add_source(vfin) ? + ssmd->attach_sink(vfin); + + /* (F) */ + // sim->add_source(kf) ? + vfin->attach_sink(kf); + + /* (G) */ + kf->attach_sink(kfs); + + /* wconfig=pywebsock.WebserverConfig(7682, False, False, False) + * web=pywebsock.Webserver.make(wconfig) + */ + g_ws = Webserver::make(WebserverConfig(7682 /*port*/, + false /*!tls_flag*/, + false /*!strict_host_check_flag*/, + false /*!use_retry_flag*/), + PrintJsonSingleton::instance()); + + /* web.register_http_endpoint(uls.http_endpoint_descr("/uls")) # /dyn/uls/snap + * web.register_http_endpoint(kfs.http_endpoint_descr("/kfs")) # /dyn/kfs/snap + */ + g_ws->register_http_endpoint(uls->http_endpoint_descr(pjson, "/uls")); + g_ws->register_http_endpoint(kfs->http_endpoint_descr(pjson, "/kfs")); + + /* web.register_stream_endpoint(src.stream_endpoint_descr("/ws/uls")) + * web.register_stream_endpoint(kf.stream_endpoint_descr("/ws/kfs")) + */ + g_ws->register_stream_endpoint(src->stream_endpoint_descr("/ws/uls")); + g_ws->register_stream_endpoint(kf->stream_endpoint_descr("/ws/kfs")); + + log("starting webserver.."); + + g_ws->start_webserver(); + + log("..webserver started"); + + /* attempt simulation. + * throttle so that we have time to connect browser, give url + * http://localhost:7682/ex_websock.html + * pywebsock/ex_websock.py implements a governed simulation loop + * in python, so that it's interruptible. + * here, we can rely on simulator instead + */ + sim->run_throttled_until(t0 /*t1 - ignored if <= sim.t0()*/, + 50 /*n_max*/, + 2.5 /*replay_factor*/); + + + log("joining webserver.."); + + g_ws->join_webserver(); + + log("..joined webserver"); + } catch (std::exception & ex) { + std::cerr << "caught exception" << xtag("ex", ex.what()) << std::endl; + } + +} /*main*/ + +/* end websock_utest_main.cpp */ From df951e4a4dea8acae4de07cfabb8db94e9628377 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:08:14 -0400 Subject: [PATCH 0258/2524] + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..eff45bd2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +build*/* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json From d312d8a5a49efa3ec4f22fd2e0e8c70f73414020 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:13:07 -0400 Subject: [PATCH 0259/2524] build: fix for header-only callback dep --- include/xo/webutil/StreamEndpointDescr.hpp | 2 +- src/webutil/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/include/xo/webutil/StreamEndpointDescr.hpp b/include/xo/webutil/StreamEndpointDescr.hpp index 78053008..53aa63c7 100644 --- a/include/xo/webutil/StreamEndpointDescr.hpp +++ b/include/xo/webutil/StreamEndpointDescr.hpp @@ -6,8 +6,8 @@ #pragma once #include "Alist.hpp" -#include "xo/callback/CallbackSet.hpp" #include "xo/refcnt/Refcounted.hpp" +#include "xo/callback/CallbackSet.hpp" #include namespace xo { diff --git a/src/webutil/CMakeLists.txt b/src/webutil/CMakeLists.txt index 57f96726..7d9f5665 100644 --- a/src/webutil/CMakeLists.txt +++ b/src/webutil/CMakeLists.txt @@ -9,6 +9,7 @@ xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # external dependencies +xo_dependency(${SELF_LIB} refcnt) xo_dependency(${SELF_LIB} callback) # end CMakeLists.txt From 77a570f8a5c8b40cada66e35352dc7fc40d3b8ad Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:13:21 -0400 Subject: [PATCH 0260/2524] github: + workflow --- .github/workflows/main.yml | 117 +++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..8b3795c9 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,117 @@ +name: build xo-callback + xo dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Configure self (webutil) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (webutil) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Test self (webutil) + working-directory: ${{github.workspace}}/build_webutil + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 6d970d4f3c944d69de69a4d15dccb8fbedfdf4be Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:16:14 -0400 Subject: [PATCH 0261/2524] drop false header-only claim ! --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34463fb0..0d906d20 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# webutil library (header-only) +# webutil library ## Getting Started From 0deb2a2ab3815e494597467af258b17a2d263896 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:20:14 -0400 Subject: [PATCH 0262/2524] github: + workflow --- .github/workflows/main.yml | 155 +++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..4c4f8a76 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,155 @@ +name: build xo-callback + xo dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + + - name: Configure self (reactor) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (reactor) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Test self (reactor) + working-directory: ${{github.workspace}}/build_reactor + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From d8efcb7490983eca7427b784dd7c841afb1cccbd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:22:41 -0400 Subject: [PATCH 0263/2524] github: fix missing subsys dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4c4f8a76..4cea3fc2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -82,6 +82,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + - name: Clone reflect uses: actions/checkout@v3 with: From 638fb898ff2a06f6bef1bd3bdc4945f88263d84f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:25:17 -0400 Subject: [PATCH 0264/2524] github: + missing printjson --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4cea3fc2..c16f0b3d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -158,6 +158,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + - name: Configure self (reactor) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From d392d5d593ecf39144691b247e92d583e18e7919 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:33:03 -0400 Subject: [PATCH 0265/2524] github: + missing randomgen dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c16f0b3d..aaef0421 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -177,6 +177,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen + + # ---------------------------------------------------------------- + - name: Configure self (reactor) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 3231cc6b4d413f4caffacd6e8a7333ebf4f847e1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:40:26 -0400 Subject: [PATCH 0266/2524] github: + workflow --- .github/workflows/main.yml | 231 +++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..656cb12c --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,231 @@ +name: build xo-callback + xo dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen + + # ---------------------------------------------------------------- + + - name: Clone reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/reactor + + - name: Configure reactor + # configure cmake for reactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor + + - name: Build reactor + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Install reactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reactor + + # ---------------------------------------------------------------- + + - name: Configure self (websock) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_websock -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (websock) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_websock --config ${{env.BUILD_TYPE}} + + - name: Test self (websock) + working-directory: ${{github.workspace}}/build_websock + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 2af96edaea1070aa83cc1d74c79f8f85ffa8b5b5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:44:49 -0400 Subject: [PATCH 0267/2524] github: + missing ordinaltree dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 656cb12c..369f536a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -120,6 +120,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + - name: Clone callback uses: actions/checkout@v3 with: From 61787846cc4a7466dc8f2621590edc705aac7868 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 16:50:10 -0400 Subject: [PATCH 0268/2524] github: build ordinaltree after its dep randomgen --- .github/workflows/main.yml | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 369f536a..7ef8b800 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -120,25 +120,6 @@ jobs: # ---------------------------------------------------------------- - - name: Clone ordinaltree - uses: actions/checkout@v3 - with: - repository: Rconybea/xo-ordinaltree - path: repo/ordinaltree - - - name: Configure ordinaltree - # configure cmake for ordinaltree in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree - - - name: Build ordinaltree - run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} - - - name: Install ordinaltree - # install into ${{github.workspace}}/local - run: cmake --install ${{github.workspace}}/build_ordinaltree - - # ---------------------------------------------------------------- - - name: Clone callback uses: actions/checkout@v3 with: @@ -215,6 +196,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + - name: Clone reactor uses: actions/checkout@v3 with: From 166cff50887494647a427aa79c3fc255ce54f76c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 17:03:38 -0400 Subject: [PATCH 0269/2524] github: + apt-get install libwebsockets-dev --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7ef8b800..5f26582f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,6 +25,10 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + - name: Install libwebsockets + # install libwebsockets. + run: sudo apt-get install -y libwebsockets-dev + # ---------------------------------------------------------------- - name: Clone xo-cmake From cbd748d909741954960e24dcb8670353509b34b8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 17:12:52 -0400 Subject: [PATCH 0270/2524] minor carveouts for libwebsockets version < 4 --- src/websock/WebsockUtil.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/websock/WebsockUtil.cpp b/src/websock/WebsockUtil.cpp index 7c7057fb..324d9f79 100644 --- a/src/websock/WebsockUtil.cpp +++ b/src/websock/WebsockUtil.cpp @@ -11,6 +11,10 @@ namespace xo { #define CASE(x) case x: return STRINGIFY(x) + /* ubuntu build (available via github actions) has older version of libwebsockets. + * typically building (e.g. via nix) with libwebsockets 4.3.2 + */ + switch (x) { CASE(LWS_CALLBACK_PROTOCOL_INIT); CASE(LWS_CALLBACK_PROTOCOL_DESTROY); @@ -44,7 +48,9 @@ namespace xo { CASE(LWS_CALLBACK_RECEIVE_CLIENT_HTTP); CASE(LWS_CALLBACK_COMPLETED_CLIENT_HTTP); CASE(LWS_CALLBACK_CLIENT_HTTP_WRITEABLE); +#if LWS_LIBRARY_VERSION_MAJOR >= 4 CASE(LWS_CALLBACK_CLIENT_HTTP_REDIRECT); +#endif CASE(LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL); CASE(LWS_CALLBACK_CLIENT_HTTP_DROP_PROTOCOL); CASE(LWS_CALLBACK_ESTABLISHED); @@ -112,7 +118,9 @@ namespace xo { CASE(LWS_CALLBACK_TIMER); CASE(LWS_CALLBACK_EVENT_WAIT_CANCELLED); CASE(LWS_CALLBACK_CHILD_CLOSING); +#if LWS_LIBRARY_VERSION_MAJOR >= 4 CASE(LWS_CALLBACK_CONNECTING); +#endif CASE(LWS_CALLBACK_VHOST_CERT_AGING); CASE(LWS_CALLBACK_VHOST_CERT_UPDATE); CASE(LWS_CALLBACK_MQTT_NEW_CLIENT_INSTANTIATED); @@ -126,8 +134,10 @@ namespace xo { CASE(LWS_CALLBACK_MQTT_CLIENT_CLOSED); CASE(LWS_CALLBACK_MQTT_ACK); CASE(LWS_CALLBACK_MQTT_RESEND); +#if LWS_LIBRARY_VERSION_MAJOR >= 4 CASE(LWS_CALLBACK_MQTT_UNSUBSCRIBE_TIMEOUT); CASE(LWS_CALLBACK_MQTT_SHADOW_TIMEOUT); +#endif CASE(LWS_CALLBACK_USER); } From 0c438ebcf56e633aff8a77106dfcc1324b04d542 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 17:21:32 -0400 Subject: [PATCH 0271/2524] build: require 4.3 in carveout --- src/websock/WebsockUtil.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/websock/WebsockUtil.cpp b/src/websock/WebsockUtil.cpp index 324d9f79..7ba1dc22 100644 --- a/src/websock/WebsockUtil.cpp +++ b/src/websock/WebsockUtil.cpp @@ -13,6 +13,8 @@ namespace xo { /* ubuntu build (available via github actions) has older version of libwebsockets. * typically building (e.g. via nix) with libwebsockets 4.3.2 + * + * see lws_config.h for version numbers vars */ switch (x) { @@ -48,7 +50,7 @@ namespace xo { CASE(LWS_CALLBACK_RECEIVE_CLIENT_HTTP); CASE(LWS_CALLBACK_COMPLETED_CLIENT_HTTP); CASE(LWS_CALLBACK_CLIENT_HTTP_WRITEABLE); -#if LWS_LIBRARY_VERSION_MAJOR >= 4 +#if (LWS_LIBRARY_VERSION_MAJOR >= 4) && (LWS_LIBRARY_VERSION_MINOR >= 3) CASE(LWS_CALLBACK_CLIENT_HTTP_REDIRECT); #endif CASE(LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL); @@ -118,7 +120,7 @@ namespace xo { CASE(LWS_CALLBACK_TIMER); CASE(LWS_CALLBACK_EVENT_WAIT_CANCELLED); CASE(LWS_CALLBACK_CHILD_CLOSING); -#if LWS_LIBRARY_VERSION_MAJOR >= 4 +#if (LWS_LIBRARY_VERSION_MAJOR >= 4) && (LWS_LIBRARY_VERSION_MINOR >= 3) CASE(LWS_CALLBACK_CONNECTING); #endif CASE(LWS_CALLBACK_VHOST_CERT_AGING); @@ -134,7 +136,7 @@ namespace xo { CASE(LWS_CALLBACK_MQTT_CLIENT_CLOSED); CASE(LWS_CALLBACK_MQTT_ACK); CASE(LWS_CALLBACK_MQTT_RESEND); -#if LWS_LIBRARY_VERSION_MAJOR >= 4 +#if (LWS_LIBRARY_VERSION_MAJOR >= 4) && (LWS_LIBRARY_VERSION_MINOR >= 3) CASE(LWS_CALLBACK_MQTT_UNSUBSCRIBE_TIMEOUT); CASE(LWS_CALLBACK_MQTT_SHADOW_TIMEOUT); #endif From e0ed57b91a175d391aca8a58a9146046e1f1eff5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 21:16:52 -0400 Subject: [PATCH 0272/2524] github: + jsoncpp dep (transitive dep of libwebsockets) --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5f26582f..b4559637 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,6 +25,10 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + - name: Install libjsoncpp (dep of libwebsockets) + # install jsoncpp + run: sudo apt-get install -y libjsoncpp-dev + - name: Install libwebsockets # install libwebsockets. run: sudo apt-get install -y libwebsockets-dev From 228c7cea971b78158f54db966ea65548733c1944 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 21:47:43 -0400 Subject: [PATCH 0273/2524] build: proper cmake deps for libwebsockets, jsoncpp --- src/websock/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/websock/CMakeLists.txt b/src/websock/CMakeLists.txt index e8aa7df2..88511c86 100644 --- a/src/websock/CMakeLists.txt +++ b/src/websock/CMakeLists.txt @@ -11,6 +11,11 @@ xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 $ xo_dependency(${SELF_LIB} reactor) xo_dependency(${SELF_LIB} webutil) +# see LibwebsocketsTargets-release.cmake for available targets +xo_external_target_dependency(${SELF_LIB} libwebsockets websockets_shared) +# see jsoncpp-namespaced-targets.cmake (maybe?) for available targets +xo_external_target_dependency(${SELF_LIB} jsoncpp jsoncpp_lib) + # note: changes to xo_dependency() calls here # must coordinate with find_dependency() calls in # xo-websock/cmake/websockConfig.cmake.in From 8fcfd185150dd61ee4d43eb5877b5ea918363080 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 21:57:23 -0400 Subject: [PATCH 0274/2524] github: try including /usr/lib/x86_64-linux-gnu in cmake prefix path --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b4559637..ced4fa08 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -245,7 +245,7 @@ jobs: - name: Configure self (websock) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_websock -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build_websock -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local;/usr/lib/x86_64-linux-gnu -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build self (websock) # Build your program with the given configuration From df2c0b3e208f2aff9cefd1d360673a9fc8bf6e0e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 22:02:26 -0400 Subject: [PATCH 0275/2524] github: try /cmake qualifier on CMAKE_PREFIX_PATH --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ced4fa08..3ea86dcd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -245,7 +245,7 @@ jobs: - name: Configure self (websock) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_websock -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local;/usr/lib/x86_64-linux-gnu -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build_websock -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local;/usr/lib/x86_64-linux-gnu/cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build self (websock) # Build your program with the given configuration From 1a72a54633df0690065bde2bb039feaea88bd42b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 22:09:44 -0400 Subject: [PATCH 0276/2524] build + github: Libwebsockets spelling --- .github/workflows/main.yml | 5 ++++- src/websock/CMakeLists.txt | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3ea86dcd..bdeb801d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -242,10 +242,13 @@ jobs: # ---------------------------------------------------------------- + - name: check stuff + run: ls /usr/lib/x86_64-linux-gnu + - name: Configure self (websock) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_websock -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local;/usr/lib/x86_64-linux-gnu/cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: cmake -B ${{github.workspace}}/build_websock -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake "-DCMAKE_PREFIX_PATH=${{github.workspace}}/local;/usr/lib/x86_64-linux-gnu/cmake" -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build self (websock) # Build your program with the given configuration diff --git a/src/websock/CMakeLists.txt b/src/websock/CMakeLists.txt index 88511c86..a1f15e18 100644 --- a/src/websock/CMakeLists.txt +++ b/src/websock/CMakeLists.txt @@ -12,7 +12,7 @@ xo_dependency(${SELF_LIB} reactor) xo_dependency(${SELF_LIB} webutil) # see LibwebsocketsTargets-release.cmake for available targets -xo_external_target_dependency(${SELF_LIB} libwebsockets websockets_shared) +xo_external_target_dependency(${SELF_LIB} Libwebsockets websockets_shared) # see jsoncpp-namespaced-targets.cmake (maybe?) for available targets xo_external_target_dependency(${SELF_LIB} jsoncpp jsoncpp_lib) From c8326018bbe13c40ec3c6ecb5528a5c3c33686d4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 17 Oct 2023 22:19:30 -0400 Subject: [PATCH 0277/2524] websock: initializer for lws_http_mount._unused[] with lws 4.0 --- .github/workflows/main.yml | 4 ++-- src/websock/Webserver.cpp | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bdeb801d..403432bb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -242,8 +242,8 @@ jobs: # ---------------------------------------------------------------- - - name: check stuff - run: ls /usr/lib/x86_64-linux-gnu + - name: check cmake-supporting packages + run: ls /usr/lib/x86_64-linux-gnu/cmake - name: Configure self (websock) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. diff --git a/src/websock/Webserver.cpp b/src/websock/Webserver.cpp index 6e1dbdb1..3f793111 100644 --- a/src/websock/Webserver.cpp +++ b/src/websock/Webserver.cpp @@ -657,6 +657,8 @@ namespace xo { virtual void init_protocols(std::vector * p_v) = 0; void init_mount_dynamic(lws_http_mount * p_mount) { + /* see lws-context-vhost.h for lws_http_mount */ + *p_mount = { /* .mount_next */ NULL, /* linked-list "next" */ /* .mountpoint */ "/dyn", /* mountpoint URL */ @@ -675,6 +677,9 @@ namespace xo { /* .origin_protocol */ LWSMPRO_CALLBACK, /* dynamic */ /* .mountpoint_len */ 4, /* char count */ /* .basic_auth_login_file */ NULL, +# if (LWS_LIBRARY_VERSION_MAJOR < 4) || (LWS_LIBRARY_VERSION_MINOR < 3) // -Werror=missing-field-initializers + /* ._unused[] */ { nullptr, nullptr }, +# endif }; } /*init_mount_dynamic*/ From b47cfebeb0f4c7b96fb3887bd52644034cedc5d8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:15:28 -0400 Subject: [PATCH 0278/2524] websock: populate lws_http_mount._unused prior to LWS version 4.3 --- src/websock/Webserver.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/websock/Webserver.cpp b/src/websock/Webserver.cpp index 3f793111..1a12fcdf 100644 --- a/src/websock/Webserver.cpp +++ b/src/websock/Webserver.cpp @@ -677,7 +677,8 @@ namespace xo { /* .origin_protocol */ LWSMPRO_CALLBACK, /* dynamic */ /* .mountpoint_len */ 4, /* char count */ /* .basic_auth_login_file */ NULL, -# if (LWS_LIBRARY_VERSION_MAJOR < 4) || (LWS_LIBRARY_VERSION_MINOR < 3) // -Werror=missing-field-initializers +# if ((LWS_LIBRARY_VERSION_MAJOR < 4) + || ((LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR < 3))) /* ._unused[] */ { nullptr, nullptr }, # endif }; @@ -704,6 +705,10 @@ namespace xo { /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */ /* .mountpoint_len */ 1, /* char count */ /* .basic_auth_login_file */ NULL, +# if ((LWS_LIBRARY_VERSION_MAJOR < 4) + || ((LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR < 3))) + /* ._unused[] */ { nullptr, nullptr }, +# endif }; } /*init_mount_static*/ From 209dfd69fc727b1b5c79ac638b82bf5abb7eaa06 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:20:07 -0400 Subject: [PATCH 0279/2524] cosmetic: formatting --- src/websock/Webserver.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/websock/Webserver.cpp b/src/websock/Webserver.cpp index 1a12fcdf..949cef16 100644 --- a/src/websock/Webserver.cpp +++ b/src/websock/Webserver.cpp @@ -677,8 +677,7 @@ namespace xo { /* .origin_protocol */ LWSMPRO_CALLBACK, /* dynamic */ /* .mountpoint_len */ 4, /* char count */ /* .basic_auth_login_file */ NULL, -# if ((LWS_LIBRARY_VERSION_MAJOR < 4) - || ((LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR < 3))) +# if ((LWS_LIBRARY_VERSION_MAJOR < 4) || ((LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR < 3))) /* ._unused[] */ { nullptr, nullptr }, # endif }; @@ -705,8 +704,7 @@ namespace xo { /* .origin_protocol */ LWSMPRO_FILE, /* files in a dir */ /* .mountpoint_len */ 1, /* char count */ /* .basic_auth_login_file */ NULL, -# if ((LWS_LIBRARY_VERSION_MAJOR < 4) - || ((LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR < 3))) +# if ((LWS_LIBRARY_VERSION_MAJOR < 4) || ((LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR < 3))) /* ._unused[] */ { nullptr, nullptr }, # endif }; From f9f37a7a486facc3bc5ea430fa0f9e5c0a340635 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:32:24 -0400 Subject: [PATCH 0280/2524] websock: compatibility for LWS without plugins --- src/websock/Webserver.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/websock/Webserver.cpp b/src/websock/Webserver.cpp index 949cef16..24594c4f 100644 --- a/src/websock/Webserver.cpp +++ b/src/websock/Webserver.cpp @@ -724,9 +724,13 @@ namespace xo { */ void init_cx_config(lws_context_creation_info * p_cx_config) { ::memset(p_cx_config, 0, sizeof(*p_cx_config)); - p_cx_config->port = this->ws_config_.port(); + p_cx_config->port = this->ws_config_.port(); p_cx_config->vhost_name = "localhost"; - p_cx_config->pvo = &(this->pvo_); +#if defined(LWS_WITH_PLUGINS) + p_cx_config->pvo = &(this->pvo_); +#else + p_cx_config->pvo = nullptr; +#endif p_cx_config->protocols = this->protocol_v_.data(); p_cx_config->mounts = &(this->mount_static_); /* userdata -- accessible from context with lws_context_user() */ From 0de914aea5ff5f9c3bb035b48f6e57ba63221130 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:38:32 -0400 Subject: [PATCH 0281/2524] websock: compile fixed for base ubuntu build --- src/websock/Webserver.cpp | 38 +++++++++++++++++++++---------------- src/websock/WebsockUtil.cpp | 6 +++--- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/websock/Webserver.cpp b/src/websock/Webserver.cpp index 24594c4f..09f90439 100644 --- a/src/websock/Webserver.cpp +++ b/src/websock/Webserver.cpp @@ -1561,25 +1561,29 @@ namespace xo { * and whether or not .per_session_data_size is 0. * .tx_packet_size */ - p_v->push_back({ - "http", - &WebserverImpl::notify_dynamic_http, - sizeof(struct per_session_data__http), - 0, - 0, - NULL, - 0 + p_v->push_back( + {"http", + &WebserverImpl::notify_dynamic_http, + sizeof(struct per_session_data__http), + 0, + 0, + NULL, + 0 }); - p_v->push_back({ - "lws-minimal", - &WebserverImplWsThread::notify_minimal, - sizeof(struct per_session_data__minimal), - 128, - 0, - NULL, - 0}); + p_v->push_back( + {"lws-minimal", + &WebserverImplWsThread::notify_minimal, + sizeof(struct per_session_data__minimal), + 128, + 0, + NULL, + 0}); /* mandatory end-of-array sentinel, requires by lws */ +#if ((LWS_LIBRARY_VERSION_MAJOR > 4) || (LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR >= 3)) p_v->push_back(LWS_PROTOCOL_LIST_TERM); +#else + p_v->push_back({ nullptr, nullptr, 0, 0, 0, nullptr, 0}); +#endif } /*init_protocols*/ /* called reentrantly from ::lws_service(), @@ -1889,6 +1893,8 @@ namespace xo { " | visit http://localhost:%d\n", this->ws_config_.port()); #if defined(LWS_WITH_PLUGINS) lwsl_user("LWS_WITH_PLUGINS present"); +#endif +#if defined(LWS_WITH_TLS) lwsl_user("LWS_WITH_TLS present"); #endif diff --git a/src/websock/WebsockUtil.cpp b/src/websock/WebsockUtil.cpp index 7ba1dc22..6cd6b19d 100644 --- a/src/websock/WebsockUtil.cpp +++ b/src/websock/WebsockUtil.cpp @@ -50,7 +50,7 @@ namespace xo { CASE(LWS_CALLBACK_RECEIVE_CLIENT_HTTP); CASE(LWS_CALLBACK_COMPLETED_CLIENT_HTTP); CASE(LWS_CALLBACK_CLIENT_HTTP_WRITEABLE); -#if (LWS_LIBRARY_VERSION_MAJOR >= 4) && (LWS_LIBRARY_VERSION_MINOR >= 3) +#if ((LWS_LIBRARY_VERSION_MAJOR > 4) || (LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR >= 3)) CASE(LWS_CALLBACK_CLIENT_HTTP_REDIRECT); #endif CASE(LWS_CALLBACK_CLIENT_HTTP_BIND_PROTOCOL); @@ -120,7 +120,7 @@ namespace xo { CASE(LWS_CALLBACK_TIMER); CASE(LWS_CALLBACK_EVENT_WAIT_CANCELLED); CASE(LWS_CALLBACK_CHILD_CLOSING); -#if (LWS_LIBRARY_VERSION_MAJOR >= 4) && (LWS_LIBRARY_VERSION_MINOR >= 3) +#if ((LWS_LIBRARY_VERSION_MAJOR > 4) || (LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR >= 3)) CASE(LWS_CALLBACK_CONNECTING); #endif CASE(LWS_CALLBACK_VHOST_CERT_AGING); @@ -136,7 +136,7 @@ namespace xo { CASE(LWS_CALLBACK_MQTT_CLIENT_CLOSED); CASE(LWS_CALLBACK_MQTT_ACK); CASE(LWS_CALLBACK_MQTT_RESEND); -#if (LWS_LIBRARY_VERSION_MAJOR >= 4) && (LWS_LIBRARY_VERSION_MINOR >= 3) +#if ((LWS_LIBRARY_VERSION_MAJOR > 4) || (LWS_LIBRARY_VERSION_MAJOR == 4) && (LWS_LIBRARY_VERSION_MINOR >= 3)) CASE(LWS_CALLBACK_MQTT_UNSUBSCRIBE_TIMEOUT); CASE(LWS_CALLBACK_MQTT_SHADOW_TIMEOUT); #endif From 51818852a4d4cb1f48387e4eecad7f8c3d300bb4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:47:12 -0400 Subject: [PATCH 0282/2524] initial implementation --- CMakeLists.txt | 52 +++ cmake/simulatorConfig.cmake.in | 13 + include/xo/simulator/EventSink.hpp | 19 + include/xo/simulator/Simulator.hpp | 299 ++++++++++++ include/xo/simulator/SourceTimestamp.hpp | 107 +++++ include/xo/simulator/TimeSlip.hpp | 39 ++ include/xo/simulator/init_simulator.hpp | 20 + src/simulator/CMakeLists.txt | 19 + src/simulator/Simulator.cpp | 550 +++++++++++++++++++++++ src/simulator/SourceTimestamp.cpp | 32 ++ src/simulator/init_simulator.cpp | 31 ++ 11 files changed, 1181 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/simulatorConfig.cmake.in create mode 100644 include/xo/simulator/EventSink.hpp create mode 100644 include/xo/simulator/Simulator.hpp create mode 100644 include/xo/simulator/SourceTimestamp.hpp create mode 100644 include/xo/simulator/TimeSlip.hpp create mode 100644 include/xo/simulator/init_simulator.hpp create mode 100644 src/simulator/CMakeLists.txt create mode 100644 src/simulator/Simulator.cpp create mode 100644 src/simulator/SourceTimestamp.cpp create mode 100644 src/simulator/init_simulator.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..8f99f0a2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +# xo-simulator/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(simulator VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# 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 + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(src/simulator) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support for reactor customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install project .hpp files + +xo_install_include_tree() + +# end CMakeLists.txt diff --git a/cmake/simulatorConfig.cmake.in b/cmake/simulatorConfig.cmake.in new file mode 100644 index 00000000..4f721e9f --- /dev/null +++ b/cmake/simulatorConfig.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +find_dependency(reactor) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/simulator/EventSink.hpp b/include/xo/simulator/EventSink.hpp new file mode 100644 index 00000000..2804b1e6 --- /dev/null +++ b/include/xo/simulator/EventSink.hpp @@ -0,0 +1,19 @@ +/* @file EventSink.hpp */ + +#pragma once + +namespace xo { + namespace sim { + /* something that observes (consumes) events of type T. + * we deliberately hide event sinks from top-level of simulator scaffold, + * so that we don't have to impose a common event type for T + */ + template + class EventSink { + public: + void operator()(T const & x); + }; /*EventSink*/ + } /*namespace sim*/ +} /*namespace xo*/ + +/* end EventSink.hpp */ diff --git a/include/xo/simulator/Simulator.hpp b/include/xo/simulator/Simulator.hpp new file mode 100644 index 00000000..d7a0baa1 --- /dev/null +++ b/include/xo/simulator/Simulator.hpp @@ -0,0 +1,299 @@ +/* @file Simulator.hpp */ + +#pragma once + +#include "xo/reactor/Reactor.hpp" +#include "SourceTimestamp.hpp" +#include "xo/reactor/ReactorSource.hpp" +#include "xo/refcnt/Refcounted.hpp" +//#include "time/Time.hpp" +#include + +namespace xo { + namespace sim { + class TimeSlip; + + /* delay state-changing simulator command while handling + * simulator events. need this to permit reentrancy + */ + struct ReentrantSimulatorCmd { + enum SimulatorCmdEnum { NotifySourcePrimed, CompleteAddSource, CompleteRemoveSource }; + + using ReactorSource = xo::reactor::ReactorSource; + + public: + ReentrantSimulatorCmd() = default; + ReentrantSimulatorCmd(SimulatorCmdEnum cmd, + ref::rp const & src) + : cmd_{cmd}, src_{src} {} + + static ReentrantSimulatorCmd notify_source_primed(ref::rp const & src) { + return ReentrantSimulatorCmd(NotifySourcePrimed, src); + } + + static ReentrantSimulatorCmd complete_add_source(ref::rp const & src) { + return ReentrantSimulatorCmd(CompleteAddSource, src); + } + + static ReentrantSimulatorCmd complete_remove_source(ref::rp const & src) { + return ReentrantSimulatorCmd(CompleteRemoveSource, src); + } + + SimulatorCmdEnum cmd() const { return cmd_; } + ref::rp const & src() const { return src_; } + + private: + /* NotifySourcePrimed: deferred Simulator.notify_source_primed(.src) + * CompleteAddSource: deferred Simulator.complete_add_source(.src) + * CompleteRemoveSource: deferred Simulator.complete_remove_source(.src) + */ + SimulatorCmdEnum cmd_ = NotifySourcePrimed; + /* if .cmd=NotifySourcePrimed|CompleteAddSource|CompleteRemoveSource: reactor source */ + ref::rp src_; + }; /*ReentrantSimulatorCmd*/ + + /* Generic simulator + * + * - time advances monotonically + * - applies a modifiable set of sources + * + * A Simulator isn't an example of a Reactor, + * because it can't work with arbitrary Sources + * (may find it expedient to fake this later, + * so we can easily adopt + * Source.notify_reactor_add() / Source.notify_reactor_remove()) + * in a simulation context + */ + class Simulator : public reactor::Reactor { + public: + using ReactorSourcePtr = xo::reactor::ReactorSourcePtr; + using ReactorSource = xo::reactor::ReactorSource; + using utc_nanos = xo::time::utc_nanos; + using nanos = xo::time::nanos; + + public: + ~Simulator(); + + static ref::rp make(utc_nanos t0); + + /* value of .t0() is estabished in ctor. + * it will not change except across call to .advance_one() + * in particular .add_source() does not change .t0() + */ + utc_nanos t0() const { return t0_; } + + /* timestamp of last event delivered */ + utc_nanos last_tm() const { return last_tm_; } + /* total #of events delivered since sim start */ + uint64_t n_event() const { return n_event_; } + + /* true iff all simulation source are exhausted + * a newly-created simulator is in the exhausted state; + * it may transition to non-exhausted state across + * call to .add_source() + */ + bool is_exhausted() const { return this->src_v_.empty(); } + + /* true iff src has been added to this simulator + * (by .add_source()) + */ + bool is_source_present(ref::brw src) const; + + /* promise: + * .next_tm() > .t0() || .is_exhausted() + * + * .next_tm() may decrease across .add_source() call + * .next_tm() may increase across .advance_one() call + */ + utc_nanos next_tm() const; + + /* returns source that will be used for next simulator event. + * nullptr if no remaining sources + */ + ReactorSource * next_src() const; + + /* cross-reference realtime to simulated time, + * for throttled replay + */ + TimeSlip timeslip() const; + + /* compute throttled real time for next event. + * caller supplies: + * 1. a pair of timesstamps xref_ts = (sim_tm, real_tm) + * - xref_ts.sim_tm is time in simulation coords of last event + * (i.e. most recent available value of .last_tm()) + * - xref_ts.real_tm is wall clock time associated with simtime + * 2. a replay factor, representing desired + * elapsed_simulation_time : elapsed_real_time + * + * return value is realtime delay to apply before next simulated event, + * in order to maintain desired replay factor + * + * The incremental api here is intended to be used from a python thread. + * + * Expect python simulation loop like: + * import pysimulator + * + * replay_factor = 1.0 + * sim = pysimulator.Simulator.make(t0) + * sim.run_one() + * tslip = sim.timeslip() + * while(True): + * dt = sim.throttled_event_dt(tslip, replay_factor) + * sleep(dt) + * sim.run_one() + * + * This allows sleep() to be invoked from python, + * which plays nicely with python threading model + */ + nanos throttled_event_dt(TimeSlip xref_ts, + double replay_factor) const; + + /* current contents of simulation heap, in increasing time order. + * copies heap to drain it in heap order + */ + std::vector heap_contents() const; + + /* print heap contents to *p_scope. intended for diagnostics */ + void log_heap_contents(xo::scope * p_scope) const; + + /* human-readable string identifying this simulator */ + std::string display_string() const; + + /* emit the first available event from a single simulation source. + * resolve ties arbitrarily. + * + * returns the #of events dispatched + * (expect this always = 1) + */ + std::uint64_t advance_one_event(); + + /* run simulation until earliest event time t satisfies t > t1 + */ + void run_until(utc_nanos t1); + + /* run simulation at realtime speed, throttling according to replay_factor, + * until either: + * - simulation exhausted + * - n events handled, if n>0 + * - sim clock reaches t1, if t1>t0 + * + * see also .run_one(), .run_until(), .run_n(), .run() + * + * note: this method not suitable for use from python wrappers; + * would hold GIL until complete. + * for that use case better to implement throttled sim loop + * in python + * + * t1. if > .t0, limit sim to events with t < t1 + * n. if > 0, sim at most n events + * replay_factor. throttle sim to keep + * {elapsed sim time} <= replay_factor * {elapsed real time} + * return. #of events simmed + */ + uint64_t run_throttled_until(utc_nanos t1, + int32_t n, + double replay_factor); + + // ----- inherited from Reactor ----- + + /* notification when nonprimed source becomes primed + */ + virtual void notify_source_primed(ref::brw src) override; + + /* add a new simulation source. + * event that precede .t0 will be discarded. + * + * returns true if src added; false if already present + */ + virtual bool add_source(ref::brw src) override; + + /* remove simulation source. + * returns true if src removed; false if was not present + * + * (not typically needed for simulations) + */ + virtual bool remove_source(ref::brw src) override; + + /* synonym for .advance_one_event() */ + virtual std::uint64_t run_one() override; + + private: + explicit Simulator(utc_nanos t0); + + /* updates source timestamp in simulation heap. + * preserves + * + * Require: + * - src->is_primed() + * - .sim_heap[.sim_heap.size - 1] already refers to src + * + * need_pop_flag, if true, src is at back of heap vector, + * need to pop before re-inserting. + */ + void heap_update_source(ReactorSource * src, + bool need_pop_flag); + + /* insert source into .sim_heap. + * increase sim_heap.size() by +1 + */ + void heap_insert_source(ReactorSource * src); + + /* complete any reentrant work encountered + * while deliverying another event + */ + void complete_delivery_work(); + + /* complete reentrant call to .add_source() */ + void complete_add_source(ref::brw src); + /* complete reentrant call to .remove_source() */ + void complete_remove_source(ref::brw src); + + friend class RaiiDeliveryWork; + + private: + /* simulation heap: + * each unexhausted source appears + * exactly once, in increasing time order of next event + * + * Invariant: + * - all sources s in .sim_heap satisfy: + * - s.is_exhausted() = false + * - s.t0() >= .t0 + */ + std::vector sim_heap_; + + /* initial simulation clock */ + utc_nanos t0_; + + /* time of most recent simulated event */ + utc_nanos last_tm_; + + /* #of simulated events handled */ + uint64_t n_event_ = 0; + + /* simulation sources + * Invariant: + * - all source s in .src_v satisfy: + * EITHER + * 1. s.is_exhausted() = true + * OR + * 2.1 s.is_exhausted() = false + * 2.2 s.t0() >= .t0 + */ + std::vector src_v_; + + /* reentrancy protection. set during .advance_one_event() */ + bool delivery_in_progress_ = false; + + /* when certain Simulator methods are invoked + * while in the midst of delivering another event, + * must defer until delivery has completed + */ + std::vector reentrant_cmd_v_; + }; /*Simulator*/ + + } /*namespace sim*/ +} /*namespace xo*/ + +/* end Simulator.hpp */ diff --git a/include/xo/simulator/SourceTimestamp.hpp b/include/xo/simulator/SourceTimestamp.hpp new file mode 100644 index 00000000..380e81c4 --- /dev/null +++ b/include/xo/simulator/SourceTimestamp.hpp @@ -0,0 +1,107 @@ +/* file SourceTimestamp.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "xo/reactor/ReactorSource.hpp" + +namespace xo { + namespace sim { + /* remember a timestamp for a simulation source; + * use to insert a source into simulation heap. + * don't want to use SimulationSource.t0, so that we can + * promise heap invariants without reying on + * any behavior of SimulationSource. + * + * Note: Need to resolve ties between different sources, + * if they coincide on timestamp of next event. + * For now use SimulationSource address + */ + class SourceTimestamp { + public: + using ReactorSource = xo::reactor::ReactorSource; + using utc_nanos = xo::time::utc_nanos; + + public: + SourceTimestamp(utc_nanos t0, + ReactorSource * src) + : t0_(t0), src_(src) {} + + static int32_t compare(SourceTimestamp const & x, + SourceTimestamp const & y) { + using xo::time::utc_nanos; + using xo::time::nanos; + + nanos dt = x.t0_ - y.t0_; + + if(dt < nanos(0)) + return -1; + else if(dt > nanos(0)) + return +1; + + /* timestamps are equal */ + + std::ptrdiff_t dptr = (x.src() - y.src()); + + return dptr; + } /*compare*/ + + utc_nanos t0() const { return t0_; } + ReactorSource * src() const { return src_; } + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* timestamp for this source */ + utc_nanos t0_; + /* simulation source + * promise: + * - src.t0() >= .t0 || src.is_exhausted + */ + ReactorSource * src_ = nullptr; + }; /*SourceTimestamp*/ + + inline bool operator==(SourceTimestamp const & x, + SourceTimestamp const & y) + { + return SourceTimestamp::compare(x, y) == 0; + } /*operator==*/ + + inline bool operator<(SourceTimestamp const & x, + SourceTimestamp const & y) + { + return SourceTimestamp::compare(x, y) < 0; + } /*operator<*/ + + inline bool operator<=(SourceTimestamp const & x, + SourceTimestamp const & y) + { + return SourceTimestamp::compare(x, y) <= 0; + } /*operator<=*/ + + inline bool operator>(SourceTimestamp const & x, + SourceTimestamp const & y) + { + return SourceTimestamp::compare(x, y) > 0; + } /*operator>*/ + + inline bool operator>=(SourceTimestamp const & x, + SourceTimestamp const & y) + { + return SourceTimestamp::compare(x, y) >= 0; + } /*operator>=*/ + + inline std::ostream & + operator<<(std::ostream & os, + SourceTimestamp const & x) + { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace sim*/ +} /*namespace xo*/ + +/* end SourceTimestamp.hpp*/ diff --git a/include/xo/simulator/TimeSlip.hpp b/include/xo/simulator/TimeSlip.hpp new file mode 100644 index 00000000..3399c8ff --- /dev/null +++ b/include/xo/simulator/TimeSlip.hpp @@ -0,0 +1,39 @@ +/* file TimeSlip.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +//#include "time/Time.hpp" + +namespace xo { + namespace sim { + /* helper class for a throttled simulation, + * where we want simulated time to evolve at a constant rate, + * relative to real elapsed time. + * + * A TimeSlip instance pins + * simulation-time coordinates to realtime coordinates + */ + class TimeSlip { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + TimeSlip(utc_nanos sim_tm, utc_nanos real_tm) + : sim_tm_{sim_tm}, real_tm_{real_tm} {} + + utc_nanos sim_tm() const { return sim_tm_; } + utc_nanos real_tm() const { return real_tm_; } + + private: + utc_nanos sim_tm_; + utc_nanos real_tm_; + }; /*TimeSlip*/ + } /*namespace sim*/ + +} /*namespace xo*/ + + +/* end TimeSlip.hpp */ diff --git a/include/xo/simulator/init_simulator.hpp b/include/xo/simulator/init_simulator.hpp new file mode 100644 index 00000000..43137575 --- /dev/null +++ b/include/xo/simulator/init_simulator.hpp @@ -0,0 +1,20 @@ +/* file init_simulator.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + enum S_simulator_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + +/* end init_simulator.hpp */ diff --git a/src/simulator/CMakeLists.txt b/src/simulator/CMakeLists.txt new file mode 100644 index 00000000..172ee4c4 --- /dev/null +++ b/src/simulator/CMakeLists.txt @@ -0,0 +1,19 @@ +# xo-simulator/src/simulator/CMakeLists.txt + +set(SELF_LIB simulator) +set(SELF_SRCS + Simulator.cpp SourceTimestamp.cpp + init_simulator.cpp) + +xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies + +# note: changes to xo_dependency() calls here +# must coordinate with find_dependency() calls +# in xo-simulator/cmake/simulatorConfig.cmake.in +# +xo_dependency(${SELF_LIB} reactor) +#xo_dependency(${SELF_LIB} webutil) +#xo_dependency(${SELF_LIB} callback) diff --git a/src/simulator/Simulator.cpp b/src/simulator/Simulator.cpp new file mode 100644 index 00000000..9835b1ed --- /dev/null +++ b/src/simulator/Simulator.cpp @@ -0,0 +1,550 @@ +/* @file Simulator.cpp */ + +//#include "time/Time.hpp" /*need this 1st for tag(., time_point)*/ +#include "init_simulator.hpp" +#include "Simulator.hpp" +#include "TimeSlip.hpp" +#include "xo/indentlog/scope.hpp" +#include +#include +#include + +namespace xo { + using xo::reactor::ReactorSource; + using xo::ref::brw; + using xo::time::utc_nanos; + using xo::time::nanos; + + namespace sim { + class RaiiDeliveryWork { + public: + RaiiDeliveryWork(Simulator * sim) : sim_{sim} { + this->sim_->delivery_in_progress_ = true; + } + + ~RaiiDeliveryWork() { + this->sim_->delivery_in_progress_ = false; + this->sim_->complete_delivery_work(); + } + + Simulator * sim_ = nullptr; + }; /*RaiiDeliveryWork*/ + + ref::rp + Simulator::make(utc_nanos t0) { + return new Simulator(t0); + } /*make*/ + + Simulator::Simulator(utc_nanos t0) : t0_(t0) + { + XO_SUBSYSTEM_REQUIRE(simulator); + } /*ctor*/ + + Simulator::~Simulator() { + scope log(XO_ENTER0(verbose), "clear heap.."); + + this->sim_heap_.clear(); + + if (log.enabled()) { + log("visit .src_v", xtag("size", this->src_v_.size())); + for (size_t i=0; isrc_v_.size(); ++i) { + log(":src_v[", i, "] ", this->src_v_[i].get()); + } + } + + log && log("clear .src_v", xtag("size", this->src_v_.size())); + + this->src_v_.clear(); + } /*dtor*/ + + bool + Simulator::is_source_present(brw src) const + { + for (ReactorSourcePtr const & s : this->src_v_) { + if (s == src) + return true; + } + + return false; + } /*is_source_pesent*/ + + utc_nanos + Simulator::next_tm() const { + if(this->sim_heap_.empty()) { + /* 0 remaining events in simulator */ + return this->t0(); + } + + return this->sim_heap_.front().t0(); + } /*next_tm*/ + + ReactorSource* + Simulator::next_src() const { + if (this->sim_heap_.empty()) { + /* 0 remaining events in simulator */ + return nullptr; + } + + return this->sim_heap_.front().src(); + } /*next_src*/ + + void + Simulator::notify_source_primed(brw src) + { + scope log(XO_ENTER1(always, src->debug_sim_flag())); + + brw sim_src + = brw::from(src); + + log && log(xtag("src", (sim_src.get() != nullptr)), + xtag("src.name", src->name())); + + if(!sim_src) + return; + + log && log(xtag("sim.name", sim_src->name()), + xtag("src.current_tm", sim_src->sim_current_tm()), + xtag("sim_heap.size", this->sim_heap_.size())); + + if (this->delivery_in_progress_) { + log && log("reentrant call to .notify_source_primed(), defer", + xtag("src.name", src->name())); + + /* defer reentrant work until delivery completes + * see .complete_delivery_work() + */ + this->reentrant_cmd_v_.push_back + (ReentrantSimulatorCmd::notify_source_primed(src.promote())); + } else { + /* inform Simulator when a source transitions from + * 'notready' to 'ready'. + * + * this means: + * - source knows its next event + * - source should be put back into .sim_heap + */ + this->heap_insert_source(sim_src.get()); + } + + //if (lscope.enabled()) + // this->log_heap_contents(&lscope); + } /*notify_source_primed*/ + + void + Simulator::complete_add_source(brw src) + { + /* also add to simulation heap */ + this->sim_heap_.push_back(SourceTimestamp(src->sim_current_tm(), + src.get())); + + /* use std::greater<> because we need a min-heap; + * smallest timestamp at the front + */ + std::push_heap(this->sim_heap_.begin(), + this->sim_heap_.end(), + std::greater()); + } /*complete_add_source*/ + + bool + Simulator::add_source(brw sim_src) + { + scope log(XO_ENTER1(always, sim_src->debug_sim_flag())); + + log && log("enter", + xtag("src", sim_src.get()), + xtag("src.name", sim_src->name())); + + if(!sim_src || this->is_source_present(sim_src)) + return false; + + log && log("advance to t0", + xtag("t0", this->t0())); + + sim_src->sim_advance_until(this->t0(), false /*!replay_flag*/); + + this->src_v_.push_back(sim_src.promote()); + + if(sim_src->is_exhausted()) { + log && log("source exhausted!"); + } else { + sim_src->notify_reactor_add(this /*reactor*/); + + log && log(xtag("src.sim_current_tm", sim_src->sim_current_tm())); + + if (sim_src->is_empty()) { + log && log("empty source, do not insert into .sim_heap"); + + /* if source is empty, don't add to sim heap yet. + * when source becomes non-empty, source will invoke + * .notify_source_primed() + * which will insert it into .sim_heap[] + */ + ; + } else if (this->delivery_in_progress_) { + log && log("reentrant add non-empty source, delay"); + + /* defer reentrant work until delivery completes + * see .complete_delivery_work() + */ + this->reentrant_cmd_v_.push_back + (ReentrantSimulatorCmd::complete_add_source(sim_src.promote())); + } else { + log && log("non-empty source, add to .sim_heap"); + + this->complete_add_source(sim_src); + } + } + + return true; + } /*add_source*/ + + void + Simulator::complete_remove_source(brw sim_src) + { + /* rebuild .sim_heap, with sim_src removed */ + std::vector sim_heap2; + + for(SourceTimestamp const & item : this->sim_heap_) { + if(item.src() == sim_src.get()) { + /* item refers to the source we are removing -> discard */ + ; + } else { + sim_heap2.push_back(item); + + std::push_heap(sim_heap2.begin(), + sim_heap2.end(), + std::greater()); + } + + /* now discard .sim_heap, replacing with sim_heap2 */ + this->sim_heap_ = std::move(sim_heap2); + } + } /*complete_remove_source*/ + + bool + Simulator::remove_source(brw sim_src) + { + scope log(XO_DEBUG(sim_src->debug_sim_flag())); + + log && log("enter", + xtag("src", sim_src.get()), + xtag("src.name", sim_src->name())); + + //brw sim_src = brw::from(src); + + if(!sim_src || !this->is_source_present(sim_src)) + return false; + + /* WARNING: O(n)implementation here */ + + if (this->delivery_in_progress_) { + /* defer reentrant work until delivery completes. + * see .complete_delivery_work() + */ + this->reentrant_cmd_v_.push_back + (ReentrantSimulatorCmd::complete_remove_source(sim_src.promote())); + } else { + this->complete_remove_source(sim_src); + } + + return true; + } /*remove_source*/ + + std::uint64_t + Simulator::run_one() { + return this->advance_one_event(); + } /*run_one*/ + + void + Simulator::heap_update_source(ReactorSource * src, bool need_pop_flag) + { + /* Require: + * .sim_heap[.sim_heap.size - 1] already refers to src + * just updating timestamp here + */ + + std::size_t simheap_z + = this->sim_heap_.size(); + + scope log(XO_DEBUG(src->debug_sim_flag()), + xtag("src.name", src->name()), + xtag("simheap_z", simheap_z), + xtag("src.sim_current_tm", src->sim_current_tm())); + + if (need_pop_flag) + this->sim_heap_.pop_back(); + /* re-insert at new timestamp */ + this->sim_heap_.push_back(SourceTimestamp(src->sim_current_tm(), src)); + + /* use std::greater<> because we need a min-heap; + * smallest timestamp at the front + */ + std::push_heap(this->sim_heap_.begin(), + this->sim_heap_.end(), + std::greater()); + } /*heap_update_source*/ + + void + Simulator::heap_insert_source(ReactorSource * src) + { + /* santify check -- src should not currently appear in heap */ + for (SourceTimestamp const & src_recd : this->sim_heap_) { + if(src_recd.src() == src) { + /* uh oh. src is already present in heap! */ + assert(false); + } + } + + // don't need this: .heap_update_source() will insert + //this->sim_heap_.push_back(SourceTimestamp(src->sim_current_tm(), src)); + + this->heap_update_source(src, false /*!need_pop_flag*/); + } /*heap_insert_source*/ + + void + Simulator::complete_delivery_work() + { + for (ReentrantSimulatorCmd const & cmd : this->reentrant_cmd_v_) { + scope log(XO_DEBUG(cmd.src() && cmd.src()->debug_sim_flag()), + "complete reentrant work", + xtag("src.name", cmd.src()->name())); + + switch (cmd.cmd()) { + case ReentrantSimulatorCmd::NotifySourcePrimed: + this->notify_source_primed(cmd.src()); + break; + case ReentrantSimulatorCmd::CompleteAddSource: + this->complete_add_source(cmd.src()); + break; + case ReentrantSimulatorCmd::CompleteRemoveSource: + this->complete_remove_source(cmd.src()); + break; + } + + //if (lscope.enabled()) + // this->log_heap_contents(&lscope); + } + + this->reentrant_cmd_v_.clear(); + } /*complete_delivery_work*/ + + TimeSlip + Simulator::timeslip() const + { + utc_nanos real_tm = std::chrono::system_clock::now(); + utc_nanos sim_tm = this->next_tm(); + + return TimeSlip(sim_tm, real_tm); + } /*timeslip*/ + + nanos + Simulator::throttled_event_dt(TimeSlip xref, + double replay_factor) const + { + if (replay_factor <= 0.0) + replay_factor = 1e-6; + + /* hi_sim_tm: simtime for next event to be handled */ + utc_nanos hi_sim_tm = this->next_tm(); + /* desired elapsed /real time/ from start of simulation to + * to when simulation handles event @ hi_sim_tm + */ + nanos sim_dt = (hi_sim_tm - xref.sim_tm()); + auto hi_real_tm = (xref.real_tm() + + std::chrono::duration_cast(sim_dt / replay_factor)); + utc_nanos now_tm = std::chrono::system_clock::now(); + + if (now_tm < hi_real_tm) + return hi_real_tm - now_tm; + else + return nanos(0); + } /*next_throttled_tm*/ + + std::vector + Simulator::heap_contents() const + { + std::vector heap = this->sim_heap_; + std::vector retval; + + while (!heap.empty()) { + retval.push_back(heap.front()); + + std::pop_heap(heap.begin(), heap.end(), + std::greater()); + heap.pop_back(); + } + + return retval; + } /*heap_contents*/ + + void + Simulator::log_heap_contents(scope * p_scope) const + { + std::vector heap = this->sim_heap_; + + p_scope->log("/ sim heap contents:"); + p_scope->log("| t0 name n_in_ev n_queued_out_ev n_out_ev"); + + while(!heap.empty()) { + SourceTimestamp const & ts = heap.front(); + + p_scope->log("|" + , " ", ts.t0() + , " ", ts.src()->name() + , " ", ts.src()->n_queued_out_ev() + , " ", ts.src()->n_out_ev()); + + std::pop_heap(heap.begin(), heap.end(), + std::greater()); + heap.pop_back(); + } + + p_scope->log("\\"); + } /*print_heap_contents*/ + + std::string + Simulator::display_string() const + { + return ""; + } /*display_string*/ + + std::uint64_t + Simulator::advance_one_event() + { + bool debug_flag = (this->loglevel() <= log_level::chatty); + + if(this->sim_heap_.empty()) { + scope log(XO_DEBUG(debug_flag)); + + /* nothing todo */ + return 0; + } + + uint32_t old_heap_z = this->sim_heap_.size(); + + /* *src is source with earliest timestamp */ + ReactorSource * src + = this->sim_heap_.front().src(); + + utc_nanos src_tm = this->sim_heap_.front().t0(); + + scope log(XO_DEBUG(debug_flag), + xtag("threshold-loglevel", this->loglevel()), + xtag("src", src != nullptr), + xtag("src.name", src->name()), + xtag("sim.src_tm", src_tm), + xtag("src.sim_current_tm", src->sim_current_tm()), + xtag("heap_z", old_heap_z)); + + /* NOTE: src.current_tm() isn't preserved across + * call to src.deliver_one() + */ + uint64_t retval = 0; + + { + RaiiDeliveryWork raii_work(this); + + retval = src->deliver_one(); + + this->last_tm_ = src_tm; + this->n_event_ += retval; + + /* note that src.t0 may have advanced */ + + /* moves just-consumed timestamp event for src + * to back of .sim_heap + */ + std::pop_heap(this->sim_heap_.begin(), + this->sim_heap_.end(), + std::greater()); + + /* now .sim_heap[.sim_heap.size() = 1].src() is src, + * with stale timestamp + */ + + if(src->is_exhausted() || src->is_notprimed()) { + /* remove src from .sim_ + * - if src->is_exhausted(), permanently + * - if src->is_notready(), until source calls + * .notify_source_ready() + */ + this->sim_heap_.pop_back(); + } else { + this->heap_update_source(src, true /*need_pop_flag*/); + } + + assert(raii_work.sim_); + } + + return retval; + } /*advance_one_event*/ + + void + Simulator::run_until(utc_nanos t1) + { + assert(!this->delivery_in_progress_); + + while(!this->is_exhausted()) { + utc_nanos t = this->next_tm(); + + if(t > t1) + break; + + this->advance_one_event(); + } /*loop until done*/ + } /*run_until*/ + + uint64_t + Simulator::run_throttled_until(utc_nanos t1, + int32_t n_max, + double replay_factor) + { + Subsystem::verify_all_initialized(); + + scope log(XO_ENTER0(info)); + + assert(!this->delivery_in_progress_); + + uint64_t n = 0; + + if(!this->is_exhausted()) { + n += this->run_one(); + } + + /* cross-reference real time with sim time */ + TimeSlip tslip = this->timeslip(); + + while(!this->is_exhausted()) { + if ((n_max > 0) && (n >= static_cast(n_max))) { + /* reached limit on #of events simmed */ + return n; + } + + if ((t1 > this->t0()) && (this->next_tm() > t1)) { + /* reached limit on sim time */ + return n; + } + + /* if sim time passing faster than realtime (scaled by replay_factor), + * wait for real elapsed time to catch up + */ + nanos wait_dt = this->throttled_event_dt(tslip, replay_factor); + + if (wait_dt > std::chrono::milliseconds(1)) { + log && log(xtag("sleep_dt", wait_dt)); + + std::this_thread::sleep_for(wait_dt); + } else { + /* don't bother throttling for period less than 1ms, linux != rtos */ + } + + n += this->run_one(); + } + + return n; + } /*run_throttled_until*/ + + } /*namespace sim*/ +} /*namespace xo*/ + +/* end Simulator.cpp */ diff --git a/src/simulator/SourceTimestamp.cpp b/src/simulator/SourceTimestamp.cpp new file mode 100644 index 00000000..c3dca025 --- /dev/null +++ b/src/simulator/SourceTimestamp.cpp @@ -0,0 +1,32 @@ +/* file SourceTimestamp.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "SourceTimestamp.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/tostr.hpp" + +namespace xo { + using xo::xtag; + using xo::tostr; + + namespace sim { + void + SourceTimestamp::display(std::ostream & os) const + { + os << "(src_)); + os << ">"; + } /*display*/ + + std::string + SourceTimestamp::display_string() const + { + return tostr(*this); + } /*display_string*/ + } /*namespace sim*/ +} /*namespace xo*/ + +/* end SourceTimestamp.cpp */ diff --git a/src/simulator/init_simulator.cpp b/src/simulator/init_simulator.cpp new file mode 100644 index 00000000..33d039c3 --- /dev/null +++ b/src/simulator/init_simulator.cpp @@ -0,0 +1,31 @@ +/* file init_simulator.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "init_simulator.hpp" +#include "xo/reactor/init_reactor.hpp" + +namespace xo { + void + InitSubsys::init() + { + } /*init*/ + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* subsystem dependencies for simulator/ */ + retval ^= InitSubsys::require(); + + /* simulator/'s own initialization code */ + retval ^= XO_SUBSYSTEM_PROVIDE(simulator, &init); + //retval ^= Subsystem::provide("simulator", &init); + + return retval; + } /*require*/ +} /*namespace xo*/ + +/* end init_simulator.cpp */ From ecd9cb5d8554a3f17262444b9e33c4df49422264 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:47:53 -0400 Subject: [PATCH 0283/2524] + .gitignore --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8ea1f615 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# lsp keep state here +.cache +# typical build directories +build +ccov +# for lsp: manual symlink to chosen build directory +compile_commands.json From f472c58591a4fd6a10729851005f45dbf8f6406d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:50:29 -0400 Subject: [PATCH 0284/2524] github: + workflow --- .github/workflows/main.yml | 231 +++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..70c00fbf --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,231 @@ +name: build xo-simulator + xo dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen + + # ---------------------------------------------------------------- + + - name: Clone reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/reactor + + - name: Configure reactor + # configure cmake for reactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor + + - name: Build reactor + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Install reactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reactor + + # ---------------------------------------------------------------- + + - name: Configure self (simulator) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_simulator -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (simulator) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_simulator --config ${{env.BUILD_TYPE}} + + - name: Test self (simulator) + working-directory: ${{github.workspace}}/build_simulator + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From ebb6378da82e16ce2db42f559906c7ead7497101 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 11:55:27 -0400 Subject: [PATCH 0285/2524] github: supply xo-ordinaltree dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 70c00fbf..53837448 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -196,6 +196,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + - name: Clone reactor uses: actions/checkout@v3 with: From 1078c49269233ebbec285ac5888df85ca6d04137 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:06:07 -0400 Subject: [PATCH 0286/2524] initial implementation --- CMakeLists.txt | 52 +++ README.md | 57 ++++ cmake/processConfig.cmake.in | 13 + include/xo/process/AbstractRealization.hpp | 20 ++ .../xo/process/AbstractStochasticProcess.hpp | 19 ++ include/xo/process/BrownianMotion.hpp | 321 ++++++++++++++++++ include/xo/process/ExpProcess.hpp | 103 ++++++ include/xo/process/LogNormalProcess.hpp | 33 ++ include/xo/process/Realizable2Process.hpp | 58 ++++ include/xo/process/Realization.hpp | 72 ++++ include/xo/process/Realization2.hpp | 91 +++++ include/xo/process/RealizationCallback.hpp | 31 ++ include/xo/process/RealizationSource.hpp | 314 +++++++++++++++++ include/xo/process/RealizationState.hpp | 42 +++ include/xo/process/RealizationTracer.hpp | 112 ++++++ include/xo/process/StochasticProcess.hpp | 73 ++++ include/xo/process/UpxEvent.hpp | 54 +++ include/xo/process/UpxToConsole.hpp | 25 ++ include/xo/process/init_process.hpp | 21 ++ src/process/BrownianMotion.cpp | 102 ++++++ src/process/CMakeLists.txt | 19 ++ src/process/ExpProcess.cpp | 69 ++++ src/process/Realization.cpp | 5 + src/process/UpxEvent.cpp | 45 +++ src/process/UpxToConsole.cpp | 15 + src/process/init_process.cpp | 40 +++ utest/CMakeLists.txt | 28 ++ utest/ProcessReflect.test.cpp | 30 ++ utest/RealizationSource.test.cpp | 261 ++++++++++++++ utest/process_utest_main.cpp | 6 + 30 files changed, 2131 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/processConfig.cmake.in create mode 100644 include/xo/process/AbstractRealization.hpp create mode 100644 include/xo/process/AbstractStochasticProcess.hpp create mode 100644 include/xo/process/BrownianMotion.hpp create mode 100644 include/xo/process/ExpProcess.hpp create mode 100644 include/xo/process/LogNormalProcess.hpp create mode 100644 include/xo/process/Realizable2Process.hpp create mode 100644 include/xo/process/Realization.hpp create mode 100644 include/xo/process/Realization2.hpp create mode 100644 include/xo/process/RealizationCallback.hpp create mode 100644 include/xo/process/RealizationSource.hpp create mode 100644 include/xo/process/RealizationState.hpp create mode 100644 include/xo/process/RealizationTracer.hpp create mode 100644 include/xo/process/StochasticProcess.hpp create mode 100644 include/xo/process/UpxEvent.hpp create mode 100644 include/xo/process/UpxToConsole.hpp create mode 100644 include/xo/process/init_process.hpp create mode 100644 src/process/BrownianMotion.cpp create mode 100644 src/process/CMakeLists.txt create mode 100644 src/process/ExpProcess.cpp create mode 100644 src/process/Realization.cpp create mode 100644 src/process/UpxEvent.cpp create mode 100644 src/process/UpxToConsole.cpp create mode 100644 src/process/init_process.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/ProcessReflect.test.cpp create mode 100644 utest/RealizationSource.test.cpp create mode 100644 utest/process_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..b21be0ba --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +# xo-process/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(process VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# 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 + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(src/process) +add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support for reactor customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install project .hpp files + +xo_install_include_tree() + +# end CMakeLists.txt diff --git a/README.md b/README.md new file mode 100644 index 00000000..154de215 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# stochastic process library + +constructive, simulation-aware models for stochastic processes + +## Getting Started + +### build + install dependencies + +build+install these first + +- xo-reactor [github.com/Rconybea/xo-reactor](https://github.com/Rconybea/xo-reactor) +- randomgen [github.com/Rconybea/randomgen](https://github.com/Rconybea/randomgen) + +# build + install + +## build +``` +$ cd process +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +## build for unit test coverage +``` +$ cd xo-process +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +## development + +### LSP support + +LSP looks for compile commands in the root of the source tree; +cmake creates them in the root of its build directory. + +``` +$ cd xo-process +$ ln -s build/compile_commands.json +``` + +## display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-process/build +$ cmake -LAH +``` diff --git a/cmake/processConfig.cmake.in b/cmake/processConfig.cmake.in new file mode 100644 index 00000000..fa80b6be --- /dev/null +++ b/cmake/processConfig.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-process/src/process/CMakeLists.txt +# +find_dependency(reflect) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/process/AbstractRealization.hpp b/include/xo/process/AbstractRealization.hpp new file mode 100644 index 00000000..e664bb0a --- /dev/null +++ b/include/xo/process/AbstractRealization.hpp @@ -0,0 +1,20 @@ +/* file AbstractRealization.hpp + * + * author: Roland Conybeare, Nov 2022 + */ + +#pragma once + +#include "xo/reflect/SelfTagging.hpp" +#include "AbstractStochasticProcess.hpp" + +namespace xo { + namespace process { + class AbstractRealization : public reflect::SelfTagging { + public: + virtual ref::rp stochastic_process() const = 0; + }; /*AbstractRealization*/ + } /*namespace process*/ +} /*namespace xo*/ + +/* end AbstractRealization.hpp */ diff --git a/include/xo/process/AbstractStochasticProcess.hpp b/include/xo/process/AbstractStochasticProcess.hpp new file mode 100644 index 00000000..b21cc8d6 --- /dev/null +++ b/include/xo/process/AbstractStochasticProcess.hpp @@ -0,0 +1,19 @@ +/* file AbstractStochasticProcess.hpp + * + * author: Roland Conybeare, Nov 2022 + */ + +#pragma once + +#include "xo/reflect/SelfTagging.hpp" + +namespace xo { + namespace process { + class AbstractStochasticProcess : public reflect::SelfTagging { + }; /*AbstractStochasticProcess*/ + } /*namespace process*/ + +} /*namespace xo*/ + + +/* end AbstractStochasticProcess.hpp */ diff --git a/include/xo/process/BrownianMotion.hpp b/include/xo/process/BrownianMotion.hpp new file mode 100644 index 00000000..317b7ec3 --- /dev/null +++ b/include/xo/process/BrownianMotion.hpp @@ -0,0 +1,321 @@ +/* @file BrownianMotion.hpp */ + +#pragma once + +#include "Realizable2Process.hpp" +#include "Realization2.hpp" +#include "RealizationState.hpp" +#include "xo/randomgen/normalgen.hpp" +#include "xo/reflect/StructReflector.hpp" +#include "xo/reflect/TaggedPtr.hpp" +#include +#include + +namespace xo { + namespace process { + class BrownianMotionBase : public Realizable2Process { + public: + using nanos = xo::time::nanos; + + public: + BrownianMotionBase(utc_nanos t0, + double volatility) + : t0_{t0}, + volatility_(volatility), + vol2_day_{(volatility * volatility) * (1.0 / 365.25)} + {} + + /* brownian motion with constant volatility at this level */ + double volatility() const { return volatility_; } + double vol2_day() const { return vol2_day_; } + + /* compute variance that accumulates over time interval dt + * for this brownian motion + */ + double variance_dt(nanos dt) const; + + // ----- needed by Realization2 ----- + + virtual ref::rp> make_realization() override = 0; + + // ----- inherited from StochasticProcess ----- + + virtual utc_nanos t0() const override { return t0_; } + + protected: + /* generate sample given a random number from N(0,1) */ + double exterior_sample_impl(utc_nanos t, + event_type const & lo, + double x0); + + protected: + /* starting time for this process */ + utc_nanos t0_; + /* annual volatility (1-year := 365.25 days) for this process */ + double volatility_ = 0.0; + /* daily variance for this brownian motion */ + double vol2_day_ = 0.0; + }; /*BrownianMotionBase*/ + + /* representation for brownian motion. + * + * starting value of zero at time t0. + * for process with volatility s, variance for horizon dt is + * V = (s^2).dt + * + * ofc this means volatility has units 1/sqrt(t) + * + * event_type: something like std::pair + * value_type: double + */ + template + class BrownianMotion : public BrownianMotionBase { + public: + using self_type = BrownianMotion; + using rstate_type = StochasticProcess::event_type; + using TaggedRcptr = reflect::TaggedRcptr; + using normalgen_type = xo::rng::normalgen; + using nanos = xo::time::nanos; + + public: + /* t0. start time, + * sdev. annual sqrt volatility + * seed. initialize pseudorandom-number generator + */ + template + static ref::rp> make(utc_nanos t0, + double sdev, + Seed const & seed) + { + return new BrownianMotion(t0, sdev, seed); + } /*make*/ + + /* reflect BrownianMotion object representation */ + static void reflect_self() { + reflect::StructReflector> sr; + + if (sr.is_incomplete()) { +#ifdef NOT_USING + REFLECT_MEMBER(sr, t0); + REFLECT_MEMBER(sr, volatility); + REFLECT_MEMBER(sr, vol2_day); +#endif + REFLECT_MEMBER(sr, rng); + } + } /*reflect_self*/ + + virtual ~BrownianMotion() = default; + + /* see .make_realization(); coordinates with that */ + void rstate_sample_inplace(nanos dt, rstate_type * p_rstate) { + utc_nanos t0 = p_rstate->first; + utc_nanos t1 = t0 + dt; + double x1_n01 = this->rng_(); + + value_type x1 = this->exterior_sample(t1, *p_rstate, x1_n01); + + *p_rstate = std::make_pair(t1, x1); + } /*rstate_sample_inplace*/ + + // ----- inherited from BrownianMotionBase ----- + + // ----- inherited from Realizable2Process<> ----- + + virtual ref::rp> make_realization() override { + rstate_type rs0 = std::make_pair(this->t0(), + this->t0_value()); + + return new ProcessRealization2(rs0, this); + } /*make_realization*/ + + virtual std::unique_ptr make_rstate() override { + rstate_type rs0 = std::make_pair(this->t0(), + this->t0_value()); + + return std::unique_ptr(new RealizationState(rs0)); + } /*make_rstate*/ + + // ----- inherited from StochasticProcess<> ----- + + virtual double t0_value() const override { return 0.0; } + + /* sample this process at time t, + * given glb sample for this process lo={t_lo, x_lo}, t>t_lo + */ + virtual value_type exterior_sample(utc_nanos t, + event_type const &lo) override; + /* sample this process at time t, + * given glb sample for this process lo={t_lo, x_lo}, + * and lub sample for this process of hi={t_hi, x_hi}, + * t_lot1} for process hitting value a, + * given preceding known value + * {t1, v1} = {lo.first, lo.second} + */ + virtual utc_nanos hitting_time(double const &a, + event_type const &lo) override; +#endif + + /* return human-readable string identifying this process */ + virtual std::string display_string() const override { + return ""; + } + + // ----- Inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override { return reflect::Reflect::make_rctp(this); } + + private: + template + BrownianMotion(utc_nanos t0, double sdev, Seed const & seed) + : BrownianMotionBase(t0, sdev), + rng_{normalgen_type::make(RngEngine(seed), + std::normal_distribution(0.0 /*mean*/, 1.0 /*sdev*/))} { + BrownianMotion::reflect_self(); + } + + private: + /* generates normally-distributed pseudorandom numbers, + * distributed according to N(0,1) + */ + normalgen_type rng_; + }; /*BrownianMotion*/ + + template + double + BrownianMotion::interior_sample(utc_nanos ts, + event_type const & lo, + event_type const & hi) + { + /* suppose we know values of a brownian motion + * at two points t1, t2: + * x1 = B(t1) + * x2 = B(t2) + * + * Want to sample B for some particular time ts in (t1,t2). + * + * First step is to de-drift B: + * + * considered sheared process B'(t): + * B'(dt) = -B(t1) + B(t1+dt) - u.dt, + * where u = (x2-x1).(t2-t1), t in (t1,t2) + * then + * B'(0) = 0 + * E[B'(dt)] = 0 + * V[B'(dt)] ~ dt + * + * so B' is also a brownian motion. + * We want to sample the conditional process + * B''(dt) = {B'(dt) | B'(t2-t1)=0} at some point ts in (0, t2-t1). + * The condition B'(t2-t1)=0 gives us: + * B(t1) + B''(t-t1) = B(t1), t=t1 + * B(t1) + B''(t-t1) = B(t2), t=t2 + * + * At ts: + * - the increment x = B(ts) - B(t1) is normally distributed, + * with variance proportional to (ts - t1). + * - the increment y = B(t2) - B(ts) is normally distributed, + * with variance proportional to (t2 - ts). + * + * Using bivariate normal prob density of two vars x,y + * with sdev sx, sy: + * + * 1 / x^2 y^2 \ + * p(x,y) = ---------- . exp | -(1/2) (------ + ------) | + * 2.pi.sx.sy \ sx^2 sy^2 / + * + * we can condition on y=-x to get conditional probability distribution + * + * 1 / x^2 . sy^2 + y^2 . sx^2 \ + * p(x) = ---------- . exp | -(1/2) --------------------------- | + * 2.pi.sx.sy \ sx^2 . sy^2 / + * + * + * 1 / x^2 . sy^2 + y^2 . sx^2 \ + * = ---------- . exp | -(1/2) --------------------------- | + * 2.pi.sx.sy \ sx^2 . sy^2 / + * + * 1 / x^2 . (sy^2 + sx^2) \ + * = ---------- . exp | -(1/2) --------------------------- | + * 2.pi.sx.sy \ sx^2 . sy^2 / + * + * / sx^2 . sy^2 \ + * let sxy = sqrt | ------------- | + * \ sx^2 + sy^2 / + * + * then + * 1 / x^2 \ + * p(x) = ---------- . exp | -(1/2) ----- | + * 2.pi.sx.sy \ sxy^2 / + * + * which is density for normal distribution with variance sxy^2, + * (scaled by constant 1 / sqrt(sx^2 + sy^2)); + * + * e.g. at midpoint between t1 and t2, is sx^2 = sy^2 = 1/2 : + * sxy^2 = 1/4 + * + * which is 1/2 the variance we'd see at midpoint if not constrained + * to B(t2)=x2 + */ + + utc_nanos lo_tm = lo.first; + double lo_x = lo.second; + utc_nanos hi_tm = hi.first; + double hi_x = hi.second; + + double t_frac = (ts - lo_tm) / (hi_tm - lo_tm); + + /* compute mean value, at t, relative to B(lo), + * of all brownian motions on [lo, hi] that + * start from B(lo) and end at B(hi), + * + * i.e. applying drift u = (x2 - x1)/(t2 - t1) two stationary BM + */ + double mean_dx = (hi_x - lo_x) * t_frac; + + /* t splits the interval [t1,t2] into two subintervals + * [t1,t] and [t,t2]. compute variances of brownian motion + * increments [t1,t], [t,t2]: + */ + double var1 = this->variance_dt(ts - lo_tm); + double var2 = this->variance_dt(hi_tm - ts); + + /* variance for B(ts) is (var1 * var2 / (var1 + var2)) */ + double vars = var1 * var2 / (var1 + var2); + + /* sample from N(0,1) */ + double xs = this->rng_(); + + /* scale for variance of B(ts) */ + double dx = ::sqrt(vars) * xs; + + double x = lo_x + mean_dx + dx; + + return x; + } /*interior_sample*/ + + template + double + BrownianMotion::exterior_sample(utc_nanos t, + event_type const & lo) + { + /* sample brownian motion starting at t0; + * offset by lo.second + */ + + double x0 = this->rng_(); + + return this->exterior_sample_impl(t, lo, x0); + } /*exterior_sample*/ + + + } /*namespace process*/ +} /*namespace xo*/ + +/* end BrownianMotion.hpp */ diff --git a/include/xo/process/ExpProcess.hpp b/include/xo/process/ExpProcess.hpp new file mode 100644 index 00000000..a49313bb --- /dev/null +++ b/include/xo/process/ExpProcess.hpp @@ -0,0 +1,103 @@ +/* @file ExpProcess.hpp */ + +#pragma once + +//#include "time/Time.hpp" +#include "StochasticProcess.hpp" +#include +#include + +namespace xo { + namespace process { + // a stochastic process + // + // S(t) + // P(t) = m.e + // + // where + // - m is a constant scale factor + // - S(t) is some already-defined-and-represented process + // + // In particular, if S(t) is brownian motion, + // then P(t) is log-normal + // + class ExpProcess : public StochasticProcess { + public: + using TaggedRcptr = reflect::TaggedRcptr; + + public: + static ref::rp make(double scale, + ref::brw> exp_proc) { + return new ExpProcess(scale, exp_proc); + } /*make*/ + + /* reflect ExpProcess object representation */ + static void reflect_self(); + + ref::brw> exponent_process() const { return exponent_process_.borrow(); } + + // ----- inherited from StochasticProcess<...> ----- + + virtual ~ExpProcess() = default; + + virtual utc_nanos t0() const override { return this->exponent_process_->t0(); } + + virtual double t0_value() const override { + return this->scale_ * ::exp(this->exponent_process_->t0_value()); + } + + /* note: lo is a sample from the exponentiated process; + * must take log to get sample from the exponent process + */ + virtual value_type exterior_sample(utc_nanos t, + event_type const & lo) override; + + /* note: lo, hi are samples from the exponentiated process; + * must take logs to get samples from the exponent process + */ + virtual value_type interior_sample(utc_nanos t, + event_type const & lo, + event_type const & hi) override { + double m + = this->scale_; + double e + = (this->exponent_process_->interior_sample + (t, + event_type(lo.first, ::log(lo.second)), + event_type(hi.first, ::log(hi.second)))); + + return m * ::exp(e); + } /*interior_sample*/ + + virtual std::string display_string() const override { + // return tostr("display_string(), ">"); + + return ""; + } /*display_string*/ + + // ----- Inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override; + + private: + ExpProcess(double scale, ref::brw exp_proc) + : scale_(scale), + exponent_process_{exp_proc.get()} { + ExpProcess::reflect_self(); + } + + private: + /* modeling + * P(t) = m.exp(E(t)) + * where: + * - m is .scale + * - E(t) is .exponent_process + */ + double scale_ = 1.0; + /* exponentiate this process */ + ref::rp> exponent_process_; + }; /*ExpProcess*/ + } /*namespace process*/ +} /*namespace xo*/ + +/* end ExpProcess.hpp */ diff --git a/include/xo/process/LogNormalProcess.hpp b/include/xo/process/LogNormalProcess.hpp new file mode 100644 index 00000000..a7a3ae1a --- /dev/null +++ b/include/xo/process/LogNormalProcess.hpp @@ -0,0 +1,33 @@ +/* LogNormalProcess.hpp */ + +#pragma once + +#include "BrownianMotion.hpp" +#include "ExpProcess.hpp" + +namespace xo { + namespace process { + + /* log-normal process -- i.e. logs follow brownian motion + */ + class LogNormalProcess { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + /* log-normal process starting at (t0, x0) */ + template + static ref::rp make(utc_nanos t0, double x0, + double sdev, Seed const & seed) { + + ref::rp> bm + = BrownianMotion::make(t0, sdev, seed); + + return ExpProcess::make(x0 /*scale*/, bm); + } /*make*/ + }; /*LogNormalProcess*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end LogNormalProcess.hpp */ diff --git a/include/xo/process/Realizable2Process.hpp b/include/xo/process/Realizable2Process.hpp new file mode 100644 index 00000000..b05e889f --- /dev/null +++ b/include/xo/process/Realizable2Process.hpp @@ -0,0 +1,58 @@ +/* file Realizable2Process.hpp + * + * author: Roland Conybeare, Nov 2022 + */ + +#pragma once + +#include "StochasticProcess.hpp" +#include "Realization2.hpp" +#include "RealizationState.hpp" + +namespace xo { + namespace process { + /* a stochastic process p that interacts with a Realization2 + * This means: + * - p defines state (Rstate) sufficient to constructively unroll/unfold + * one of its own paths + * - p provides methods to implement such unfolding: + * - .make_realization() :: Realization2 + * create new realization of p. + * - .rstate_sample(t1,rs0) :: time x Rstate -> Rstate + * given runstate rs0 representing process state at some time t0, + * sample process at time t1, with t0<=t1 + * - in general can only sample process at a bounded set of points; + * sometimes useful to be able to generate samples consistently in + * non-monotonically-increasing time order. Algorithm to do this available + * for some, but not all p + * - .rstate_insample(t1,rs0,rs2) :: time x Rstate x Rstate -> Rstate + * given runstates rs0, rs2 representing process state at two times t0 + class Realizable2Process : public StochasticProcess { + public: + virtual ref::rp> make_realization() = 0; + /* make_rstate() will be used to establish nested state when a process is used + * as input to a transforming process (ex: ExpProcess). + * in that context the outer process' realization state will + * need to hold an abstract pointer to nested process' realization state, + * and use this method to establish that state. + */ + virtual std::unique_ptr make_rstate() = 0; + // Rstate rstate_init() const; + // void rstate_sample_implace(utc_nanos t1, Rstate * p_rs0 const; + }; /*Realizable2Process*/ + + } /*namespace process*/ +} /*namespace xo*/ + + +/* end Realizable2Process.hpp */ diff --git a/include/xo/process/Realization.hpp b/include/xo/process/Realization.hpp new file mode 100644 index 00000000..571b7b5a --- /dev/null +++ b/include/xo/process/Realization.hpp @@ -0,0 +1,72 @@ +/* @file Realization.hpp */ + +#pragma once + +#include "StochasticProcess.hpp" +//#include "time/Time.hpp" +//#include +#include +#include + +namespace xo { + namespace process { + +// realization of a stochastic process. +// interface designed to allow for lazy evaluation. +// +// since a process connects a family of random variables, +// a single process can have a generally unbounded number of distinct realizations. +// +// implications: +// - can only realize (or observe) a finite set of instants. +// - given process evolves continuously, +// want ability to revisit intervals that may already contain some realized instants. +// - achieve this by allowing for caching behavior +// + template + class Realization : public ref::Refcount { + public: + using utc_nanos = xo::time::utc_nanos; + using KnownMap = std::map; + using KnownIterator = typename KnownMap::const_iterator; + //using KnownRange = boost::iterator_range; + using KnownRange = decltype(std::views::all(KnownMap())); + + public: + static ref::rp make(ref::brw> p) { + return new Realization(p); + } /*make*/ + + ref::brw> process() const { return process_; } + + utc_nanos t0() const { return process_->t0(); } + + size_t n_known() const { return this->known_map_.size(); } + + /* require: .n_known() > 0 */ + utc_nanos lo_tm() const { return this->known_map_.begin().first(); } + utc_nanos hi_tm() const { return this->known_map_.rbegin().first(); } + + //KnownRange known_range() const { return boost::make_iterator_range(this->known_map_); } + KnownRange known_range() const { return std::views::all(this->known_map_); } + + // concept: + // realized_range() -> iterator_range + + private: + Realization(ref::brw> p) : process_{p} {} + + private: + /* stochastic process from which this realization is sampled */ + ref::rp> process_; + + /* process value (for this realization) has been established (sampled) + * at each time t in {.known_map[].first} + */ + KnownMap known_map_; + }; /*Realization*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end Realization.hpp */ diff --git a/include/xo/process/Realization2.hpp b/include/xo/process/Realization2.hpp new file mode 100644 index 00000000..499d815b --- /dev/null +++ b/include/xo/process/Realization2.hpp @@ -0,0 +1,91 @@ +/* file Realization2.hpp + * + * author: Roland Conybeare, Nov 2022 + */ + +#pragma once + +#include "AbstractRealization.hpp" +#include "xo/reflect/Reflect.hpp" +//#include "time/Time.hpp" + +namespace xo { + namespace process { + template + class Realization2 : public AbstractRealization { + }; /*Realization2*/ + + /* Rstate: state needed to trace unfolding of a process + * realization; will be process-specific. + * + * Pattern like: + * StochasticProcess p + * Rstate rs + * Realization2 rz + * + * +----+ +----+ + * | rz +----| rs | + * +--+-+ +----+ + * | ^ + * +----+ | + * | p | <----/ + * +----+ + * + * rz owns rs, sends it to p to be modified as p needs + * p knows type Rstate, initially creates it + * therefore also knows how to create its own realizations + * + * + * Require: + * - Process -isa-> Realizable2Process + */ + template + class ProcessRealization2 : public Realization2 { + public: + using TaggedRcptr = reflect::TaggedRcptr; + using nanos = xo::time::nanos; + + public: + ProcessRealization2(Rstate const & rstate, ref::rp const & process) + : rstate_{rstate}, process_{process} {} + ProcessRealization2(Rstate && rstate, ref::rp const & process) + : rstate_{std::move(rstate)}, process_{process} {} + + Rstate const & rstate() const { return rstate_; } + ref::rp const & process() const { return process_; } + + /* sample process at point .rstate.tk + dt + * Require: + * - dt >= 0 + */ + void advance_dt(nanos dt) { + this->process_->rstate_sample_inplace(dt, &(this->rstate_)); + } /*advance_dt*/ + + // ----- inherited from AbstractRealization ----- + + virtual ref::rp stochastic_process() const override { + return process_; + } /*stochastic_process*/ + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override { return reflect::Reflect::make_rctp(this); } + + private: + /* realization state + * this type is determined by .process; + * sufficient state to develop faithful realization + */ + Rstate rstate_; + /* process (set of paths + probability measure); + * *this coordinates with .process to constructively samples one such path + */ + ref::rp process_; + }; /*ProcessRealization2*/ + } /*namespace process*/ + +} /*namespace xo*/ + + +/* end Realization2.hpp */ diff --git a/include/xo/process/RealizationCallback.hpp b/include/xo/process/RealizationCallback.hpp new file mode 100644 index 00000000..314bdc2e --- /dev/null +++ b/include/xo/process/RealizationCallback.hpp @@ -0,0 +1,31 @@ +/* @file RealizationCallback.hpp */ + +#pragma once + +#include "xo/reactor/Sink.hpp" +#include "xo/indentlog/print/pair.hpp" +//#include "time/Time.hpp" +#include + +namespace xo { + namespace process { + /* callback for consuming stochastic process realizations */ + template + class RealizationCallback : public reactor::Sink1> { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + /* notification with process event (std::pair) + * see StochasticProcess::event_type + */ + virtual void notify_ev(std::pair const & ev) override; + + /* CallbackSet invokes these on add/remove events */ + virtual void notify_add_callback() override {} + virtual void notify_remove_callback() override {} + }; /*RealizationCallback*/ + } /*namespace process*/ +} /*namespace xo*/ + +/* end RealizationCallback.hpp */ diff --git a/include/xo/process/RealizationSource.hpp b/include/xo/process/RealizationSource.hpp new file mode 100644 index 00000000..f5087942 --- /dev/null +++ b/include/xo/process/RealizationSource.hpp @@ -0,0 +1,314 @@ +/* @file RealizationSimSource.hpp */ + +#pragma once + +#include "xo/reactor/ReactorSource.hpp" +#include "RealizationTracer.hpp" +#include "RealizationCallback.hpp" +#include "xo/callback/CallbackSet.hpp" +#include "xo/indentlog/scope.hpp" +#include + +namespace xo { + namespace process { + /* use a discrete realization of a continuous stochastic process, + * as a simulation source. + * + * 1. Realization is developed lazily, (see RealizationTracer) + * 2. Use a fixed discretization interval to develop realization + * 3. events are consumed by Sink + * + * Require: + * - std::pair --convertible-to--> EventType + * - EventSink.notify_source_exhausted() + * - invoke EventSink(x), with x :: EventType + */ + template + class RealizationSourceBase : public xo::reactor::ReactorSource { + public: + using event_type = typename RealizationTracer::event_type; + using nanos = xo::time::nanos; + + public: + ~RealizationSourceBase() { + //constexpr char const * c_self = "RealizationSimSource<>::dtor"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled), + "delete instance", + xtag("p", this)); + } /*dtor*/ + + static ref::rp + make(ref::rp> const & tracer, + nanos ev_interval_dt, + EventSink const & ev_sink) + { + using xo::scope; + using xo::xtag; + + constexpr bool c_logging_enabled = false; + + auto p = new RealizationSourceBase(tracer, ev_interval_dt, ev_sink); + + scope log(XO_DEBUG(c_logging_enabled), + "create instance", + xtag("p", p), + xtag("bytes", sizeof(RealizationSourceBase))); + + return p; + } /*make*/ + +#ifdef NOT_IN_USE + static ref::rp make(ref::rp> tracer, + nanos ev_interval_dt, + EventSink && ev_sink) + { + return new RealizationSimSource(tracer, ev_interval_dt, ev_sink); + } /*make*/ +#endif + + event_type const & current_ev() const { return this->tracer_->current_ev(); } + nanos ev_interval_dt() const { return ev_interval_dt_; } + + /* supplying this to allow for setting up cyclic pointer references */ + EventSink * ev_sink_addr() { return &(this->ev_sink_); } + + /* deliver current event to sink */ + void sink_one() const { + /* calling .ev_sink() can modify the callback set reentrantly + * (i.e. adding/removing callbacks) + * although this changes the state of .ev_sink, + * we want to treat this as not changing the state of *this + */ + RealizationSourceBase * self = const_cast(this); + + self->ev_sink_(this->tracer_->current_ev()); + } /*sink_one*/ + + // ----- inherited from ReactorSource ----- + + /* process realizations are always primed (at least for now) */ + virtual bool is_empty() const override { return false; } + /* stochastic process api doesn't have an end time; + * will need simulator to impose one + */ + virtual bool is_exhausted() const override { return false; } + + virtual utc_nanos sim_current_tm() const override { return this->tracer_->current_tm(); } + + virtual std::string const & name() const override { return name_; } + virtual void set_name(std::string const & x) override { this->name_ = x; } + virtual bool debug_sim_flag() const override { return debug_sim_flag_; } + virtual void set_debug_sim_flag(bool x) override { this->debug_sim_flag_ = x; } + + /* note: + * with replay_flag=true, treats tm as lower bound + */ + virtual std::uint64_t sim_advance_until(utc_nanos tm, bool replay_flag) override { + std::uint64_t retval = 0ul; + + if(replay_flag) { + while(this->sim_current_tm() < tm) { + retval += this->deliver_one(); + } + } else { + this->tracer_->advance_until(tm); + } + + return retval; + } /*advance_until*/ + + // ----- Inherited from AbstractSource ----- + + virtual TypeDescr source_ev_type() const override { + return reflect::Reflect::require(); + } + + /* Tracer is intended always to deliver non-volatile events */ + virtual bool is_volatile() const override { return false; } + + virtual uint32_t n_out_ev() const override { return n_out_ev_; } + /* no mechanism in RealizationSource to hold onto an outgoing event + * see reactor::SecondarySource for contrary example + */ + virtual uint32_t n_queued_out_ev() const override { return 0; } + + virtual std::uint64_t deliver_one() override { + ++(this->n_out_ev_); + this->sink_one(); + this->tracer_->advance_dt(this->ev_interval_dt_); + + return 1; + } /*deliver_one*/ + + virtual CallbackId attach_sink(ref::rp const & /*sink*/) override { + /* see RealizationSource */ + assert(false); + return CallbackId(); + } + + virtual void detach_sink(CallbackId /*id*/) override { + /* see RealizationSource */ + assert(false); + } + + virtual void display(std::ostream & os) const override { + using xo::xtag; + + os << "name()) + << xtag("n_out_ev", this->n_out_ev()) + //<< xtag("ev_interval_dt", ev_interval_dt_) + << ">"; + } /*display*/ + + virtual void visit_direct_consumers(std::function)> const &) override { + assert(false); + } + + protected: + RealizationSourceBase(ref::rp> const & tracer, + nanos ev_interval_dt, + EventSink const & ev_sink) + : tracer_{tracer}, + ev_sink_{std::move(ev_sink)}, + ev_interval_dt_{ev_interval_dt} {} + RealizationSourceBase(ref::rp> const & tracer, + nanos ev_interval_dt, + EventSink && ev_sink) + : tracer_{tracer}, + ev_sink_{std::move(ev_sink)}, + ev_interval_dt_(ev_interval_dt) {} + + private: + static constexpr std::string_view sc_self_type = xo::reflect::type_name>(); + + private: + /* reporting name for this source -- use when .debug_sim_flag is set */ + std::string name_; + /* if true reactor/simulator to log interaction with this source */ + bool debug_sim_flag_ = false; + /* counts lifetime #of events */ + uint32_t n_out_ev_ = 0; + /* produces events representing realized stochastic-process values */ + ref::rp> tracer_; + /* send stochastic-process events to this sink */ + EventSink ev_sink_; + /* discretize process using this interval: + * consecutive events from this simulation source will be at least + * .ev_interval_dt apart + */ + nanos ev_interval_dt_; + }; /*RealizationSourceBase*/ + + // ----- RealizationSource ----- + + template + class RealizationSource + : public RealizationSourceBase, + decltype(&reactor::Sink1::notify_ev)>> + + { + public: + using TypeDescr = reflect::TypeDescr; + using CallbackId = fn::CallbackId; + using utc_nanos = xo::time::utc_nanos; + using nanos = xo::time::nanos; + + public: + static ref::rp> make(ref::rp> const & tracer, + nanos ev_interval_dt) + { + return new RealizationSource(tracer, ev_interval_dt); + } /*make*/ + + CallbackId add_callback(ref::rp> const & cb) { + return this->ev_sink_addr()->add_callback(cb); + } /*add_callback*/ + + void remove_callback(CallbackId id) { + this->ev_sink_addr()->remove_callback(id); + } /*remove_callback*/ + + // ----- inherited from AbstractSource ----- + + /* alternative naming: + * .add_callback(sink) <--> .attach_sink(sink) + * .remove_callback(sink) <--> .detach_sink(sink) + */ + virtual CallbackId attach_sink(ref::rp const & sink) override { + /* ------- + * WARNING + * ------- + * spent some time chasing down clang behavior here. + * the call to + * reactor::Sink1<...>::require_native() + * fails unexpectedly because the template + * Sink1> + * and RealizationSource may come from different modules. + */ + + //using xo::scope; + //using xo::xtag; + + /* checking that sink handles events of type T + * This is quick-n-dirty. Want reflection here, so we can write + * a runtime type test + * sink->can_consume() + * w/out exploding vtable size + */ + constexpr std::string_view c_self_name + = "RealizationSource::attach_sink"; + + //scope lscope(c_self_name); + //lscope.log(xtag("T", reflect::type_name())); + + ref::rp> event_sink + = reactor::Sink1::require_native(c_self_name, sink); + + return this->add_callback(event_sink); + } /*attach_sink*/ + + virtual void detach_sink(CallbackId id) override { + /* see comment on .attach_sink() */ + + this->remove_callback(id); + } /*detach_sink*/ + + virtual void display(std::ostream & os) const override { + using xo::xtag; + + os << "name()) + << xtag("n_out_ev", this->n_out_ev()) + //<< xtag("ev_interval_dt", this->ev_interval_dt()) + << ">"; + } /*display*/ + + // ----- Inherited from AbstractEventProcessor ----- + + virtual void visit_direct_consumers(std::function)> const & fn) override { + + for(auto const & x : *(this->ev_sink_addr())) + fn(x.fn_.borrow()); + } /*visit_direct_consumers*/ + + private: + RealizationSource(ref::rp> const & tracer, + nanos ev_interval_dt) + : RealizationSourceBase + , + decltype(&reactor::Sink1::notify_ev)> + >(tracer, + ev_interval_dt, + fn::make_notify_cbset(&reactor::Sink1::notify_ev)) + {} + }; /*RealizationSource*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end RealizationSource.hpp */ diff --git a/include/xo/process/RealizationState.hpp b/include/xo/process/RealizationState.hpp new file mode 100644 index 00000000..0d43c581 --- /dev/null +++ b/include/xo/process/RealizationState.hpp @@ -0,0 +1,42 @@ +/* file RealizationState.hpp + * + * author: Roland Conybeare, Nov 2022 + */ + +#pragma once + +#include + +/* opaque type representing state of an unfolding + * realization, for a StochasticProcess. + * Needs runtime polymorphism here so we can stack states + * e.g. to represent realization state for a process + * defined by transformation of another process. + * For example see ExpProcess. + * For now we don't refcount these; expect each process-realization + * to create its own stack, managed with unique_ptr<> + * + * See also: + * - ProcessRealization2 + * - Realizable2Process + */ +class AbstractRealizationState { +public: + AbstractRealizationState() = default; + + virtual ~AbstractRealizationState() = default; +}; /*RealizationState*/ + +template +class RealizationState : public AbstractRealizationState { +public: + RealizationState(Rstate const & rs) : rstate_{rs} {} + RealizationState(Rstate && rs) : rstate_{std::move(rs)} {} + + Rstate * p_rstate() { return &rstate_; } + +private: + Rstate rstate_; +}; /*RealizationState*/ + +/* end RealizationState.hpp */ diff --git a/include/xo/process/RealizationTracer.hpp b/include/xo/process/RealizationTracer.hpp new file mode 100644 index 00000000..a654b577 --- /dev/null +++ b/include/xo/process/RealizationTracer.hpp @@ -0,0 +1,112 @@ +/* @file RealizationTracer.hpp */ + +#pragma once + +#include "StochasticProcess.hpp" +#include "xo/refcnt/Refcounted.hpp" + +namespace xo { + namespace process { + //template class RealizationSimSource; + + /* One-way iteration over a realization (i.e. sampled path) + * belonging to a stochastic process. + * has a monotonically increasing 'current time'. + * can be adapted as a simulation source + * + * Example: + * utc_nanos t0 = ...; + * double sdev = 1.0; + * Seed seed; + * auto process = LogNormalProcess::make(t0, sdev, seed); + * auto tracer = RealizationTracer::make(process.get()); + */ + template + class RealizationTracer : public ref::Refcount { + public: + using Process = xo::process::StochasticProcess; + using process_type = Process; + /* something like std::pair */ + using event_type = typename Process::event_type; + using utc_nanos = xo::time::utc_nanos; + using nanos = xo::time::nanos; + + public: + static ref::rp make(ref::rp const & p) { + return new RealizationTracer(p); + } + + event_type const & current_ev() const { return current_; } + utc_nanos current_tm() const { return current_.first; } + /* value of this path at time t */ + T const & current_value() const { return current_.second; } + ref::rp const & process() const { return process_; } + + /* sample with fixed time: + * - advance to time t+dt, where t=.current_tm() + * - return new time and process value + * + * can use .advance_dt(dt) to avoid copying T + */ + std::pair next_dt(nanos dt) { + this->advance_dt(dt); + + return this->current_; + } /*next_dt*/ + + std::pair next_eps(double eps) { + this->advance_eps(eps); + + return this->current_; + } /*next_eps*/ + + /* sample with fixed time: + * - advance to point t+dt, with dt specified. + */ + void advance_dt(nanos dt) { + utc_nanos t1 = this->current_.first + dt; + + this->advance_until(t1); + } /*advance_dt*/ + + void advance_until(utc_nanos t1) { + event_type ev0 = this->current_; + + if(t1 <= ev0.first) { + /* tracer state already past t1 */ + } else { + T x1 = this->process_->exterior_sample(t1, ev0); + + /* careful! may not alter .current until after call to exterior_sample() + * returns + */ + + this->current_.first = t1; + this->current_.second = x1; + } + } /*advance_until*/ + +#ifdef NOT_IN_USE // need StochasticProcess.hitting_time() for this + /* sample with max change in process value eps. + * requires that T defines a norm under which eps + * can be interpreted + */ + virtual void advance_eps(double eps) = 0; +#endif + + private: + RealizationTracer(ref::rp const & p) + : current_(event_type(p->t0(), p->t0_value())), process_(p) {} + + private: + /* current (time, processvalue) associated with this realization */ + event_type current_; + + /* develop a sampled realization of this stochastic process */ + ref::rp process_; + }; /*RealizationTracer*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end RealizationTracer.hpp */ diff --git a/include/xo/process/StochasticProcess.hpp b/include/xo/process/StochasticProcess.hpp new file mode 100644 index 00000000..3c6a4101 --- /dev/null +++ b/include/xo/process/StochasticProcess.hpp @@ -0,0 +1,73 @@ +/* @file StochasticProcess.hpp */ + +#pragma once + +#include "AbstractStochasticProcess.hpp" +//#include "refcnt/Refcounted.hpp" +//#include "time/Time.hpp" +#include +#include + +namespace xo { + namespace process { + +// abstraction for a stochastic process. +// - represents a probability space: +// - a collection of paths +// - an associated probability measure on path sapce +// - paths may vary continuously with time +// - need not be continuous +// - want to be able to use in simulation, +// in which case will likely require some discretization +// + template + class StochasticProcess : public AbstractStochasticProcess { + public: + using value_type = T; + using utc_nanos = xo::time::utc_nanos; + using event_type = std::pair; + + public: + virtual ~StochasticProcess() = default; + + /* starting time for this process */ + virtual utc_nanos t0() const = 0; + + /* starting value of this process */ + virtual T t0_value() const = 0; + + /* sample this process at time t, + * given preceding known value + * {t1, v1} + * with t1 < t + */ + virtual value_type exterior_sample(utc_nanos t, + event_type const & lo) = 0; + + /* sample this process at time t, + * given surrounding known values + * {t1, v1}, {t2, v2} + * with t1 < t < t2 + */ + virtual value_type interior_sample(utc_nanos t, + event_type const & lo, + event_type const & hi) = 0; + +#ifdef NOT_IN_USE + /* sample hitting time + * T(a) = inf{t : P(t)=a, t>t1} for process hitting value a, + * given preceding known value + * {t1, v1} = {lo.first, lo.second} + */ + virtual utc_nanos hitting_time(T const & a, + event_type const & lo) = 0; +#endif + + /* human-readable string identifying this process */ + virtual std::string display_string() const = 0; + }; /*StochasticProcess*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end StochasticProcess.hpp */ diff --git a/include/xo/process/UpxEvent.hpp b/include/xo/process/UpxEvent.hpp new file mode 100644 index 00000000..51ad768b --- /dev/null +++ b/include/xo/process/UpxEvent.hpp @@ -0,0 +1,54 @@ +/* @file UpxEvent.hpp */ + +#pragma once + +#include "xo/indentlog/timeutil/timeutil.hpp" + +namespace xo { + namespace process { + /* typical representation for events emitted by a stochastic process + * writing this as a non-template class (instead of just template alias) + * because we want typeinfo to be generated + */ + class UpxEvent { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + UpxEvent(); + //UpxEvent(std::pair const & x) : contents_{x} {} + UpxEvent(std::pair const & x) : tm_{x.first}, upx_{x.second} {} + //UpxEvent(utc_nanos tm, double x) : contents_{tm, x} {} + UpxEvent(utc_nanos tm, double x) : tm_{tm}, upx_{x} {} + + /* reflect UpxEvent object representation */ + static void reflect_self(); + + /* convenience -- e.g. so we can use with EventTimeFn */ + //utc_nanos tm() const { return contents_.first; } + utc_nanos tm() const { return tm_; } + //double upx() const { return contents_.second; } + double upx() const { return upx_; } + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* note: earlier version inherited std::pair<>, but this exposed + * pybind11 problem when we tried to control printing + */ + utc_nanos tm_; + double upx_; + //std::pair contents_; + }; /*UpxEvent*/ + + inline std::ostream & + operator<<(std::ostream & os, UpxEvent const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end UpxEvent.hpp */ diff --git a/include/xo/process/UpxToConsole.hpp b/include/xo/process/UpxToConsole.hpp new file mode 100644 index 00000000..50c259fd --- /dev/null +++ b/include/xo/process/UpxToConsole.hpp @@ -0,0 +1,25 @@ +/* @file UpxToConsole.hpp */ + +#pragma once + +#include "UpxEvent.hpp" +#include "xo/reactor/Sink.hpp" + +namespace xo { + namespace process { + /* trivial extension of SinkToConsole. + * hoping to workaroudn a typeinfo problem by getting typeinfo for Sink1 + * to appear in the process/ library instead of the process_py/ library. + * + * See FAQ "dynamic_cast *> fails unexpectedly for a template class" + */ + class UpxToConsole : public xo::reactor::SinkToConsole { + public: + UpxToConsole(); + + static ref::rp make(); + }; /*UpxToConsole*/ + } /*namespace process*/ +} /*namespace xo*/ + +/* end UpxToConsole.hpp */ diff --git a/include/xo/process/init_process.hpp b/include/xo/process/init_process.hpp new file mode 100644 index 00000000..5fcba67d --- /dev/null +++ b/include/xo/process/init_process.hpp @@ -0,0 +1,21 @@ +/* file init_process.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + /* tag to represent the process/ subsystem within ordered initialization */ + enum S_process_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + +/* end init_process.hpp */ diff --git a/src/process/BrownianMotion.cpp b/src/process/BrownianMotion.cpp new file mode 100644 index 00000000..0c000696 --- /dev/null +++ b/src/process/BrownianMotion.cpp @@ -0,0 +1,102 @@ +/* @file BrownianMotion.cpp */ + +#include "xo/reflect/TaggedPtr.hpp" +//#include "time/Time.hpp" +#include "BrownianMotion.hpp" +#include + +namespace xo { + using xo::time::utc_nanos; + using xo::scope; + using xo::xtag; + + namespace process { + double + BrownianMotionBase::variance_dt(nanos dt) const + { + constexpr uint64_t c_sec_per_day = (24L * 3600L); + constexpr double c_day_per_sec = (1.0 / c_sec_per_day); + + /* time-to-horizon in nanos */ + double dt_sec = std::chrono::duration(dt).count(); + double dt_day = dt_sec * c_day_per_sec; + + return this->vol2_day_ * dt_day; + } /*variance_dt*/ + + double + BrownianMotionBase::exterior_sample_impl(utc_nanos t, + BrownianMotionBase::event_type const & lo, + double x0) + { + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + /* sample brownian motion starting at t0; + * offset by lo.second + */ + + utc_nanos lo_tm = lo.first; + double lo_x = lo.second; + + nanos dt = (t - lo_tm); + + /* variance at horizon t, relative to value at lo.first */ + double var = this->variance_dt(dt); + + /* scale for variance of B(t) - B(lo) */ + double dx = ::sqrt(var) * x0; + + double sample = lo_x + dx; + + log && log("result", + xtag("start-time", this->t0()), + xtag("vol2-day", this->vol2_day()), + xtag("lo.tm", lo_tm), + xtag("lo.x", lo_x), + xtag("dt-us", std::chrono::duration_cast(dt).count()), + xtag("var", var), + xtag("dx", dx)); + + return sample; + } /*exterior_sample_impl*/ + + // ----- BrownianMotion ----- + +#ifdef NOT_IN_USE + utc_nanos + BrownianMotion::hitting_time(double const & a, + event_type const & lo) + { + /* (1) + * probability density function p1(s) + * giving hitting time for brownian motion starting at 0, + * first time to reach a constant barrier a: + * + * a^2 + * - --- + * a 2.s + * p1(s) = ------------- . e + * sqrt(2.pi.s^3) + * + * (2) + * we also know probability density function p2(s) + * giving hitting time for brownian motion starting at 0, + * first time to reach expanding barrier a + ct: + * (i.e. T2 = inf{t : B(t) = c.t + a, t > 0}) + * + * (c.s + a)^2 + * - ----------- + * a 2.s + * p2(s) = -------------- . e + * sqrt(2.pi.s^3) + * + */ + } /*hitting_time*/ +#endif + + } /*namespace process*/ +} /*namespace xo*/ + +/* end BrownianMotion.cpp */ diff --git a/src/process/CMakeLists.txt b/src/process/CMakeLists.txt new file mode 100644 index 00000000..c6033dde --- /dev/null +++ b/src/process/CMakeLists.txt @@ -0,0 +1,19 @@ +# xo-process/src/process/CMakeLists.txt + +set(SELF_LIB process) +set(SELF_SRCS + BrownianMotion.cpp ExpProcess.cpp Realization.cpp UpxEvent.cpp UpxToConsole.cpp + init_process.cpp) + +xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies + +# note: changes to xo_dependency() calls here +# must coordinate with find_dependency() calls +# in xo-process/cmake/processConfig.cmake.in +# +xo_dependency(${SELF_LIB} reflect) +#xo_dependency(${SELF_LIB} webutil) +#xo_dependency(${SELF_LIB} callback) diff --git a/src/process/ExpProcess.cpp b/src/process/ExpProcess.cpp new file mode 100644 index 00000000..7b5d36f7 --- /dev/null +++ b/src/process/ExpProcess.cpp @@ -0,0 +1,69 @@ +/* @file ExpProcess.cpp */ + +#include "xo/reflect/TaggedPtr.hpp" +#include "xo/reflect/StructReflector.hpp" +//#include "time/Time.hpp" +#include "ExpProcess.hpp" + +namespace xo { + using reflect::Reflect; + using reflect::StructReflector; + using reflect::TaggedRcptr; + using xo::scope; + using xo::xtag; + + namespace process { + void + ExpProcess::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, scale); + REFLECT_MEMBER(sr, exponent_process); + } + } /*self_reflect*/ + + /* note: lo is a sample from the exponentiated process; + * must take log to get sample from the exponent process + */ + ExpProcess::value_type + ExpProcess::exterior_sample(utc_nanos t, + event_type const & lo) + { + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG(c_logging_enabled)); + + double lo_value = lo.second; + double log_lo_value = ::log(lo.second / this->scale_); + + double e + = (this->exponent_process_->exterior_sample + (t, + event_type(lo.first, log_lo_value))); + + double retval = this->scale_ * ::exp(e); + + log && log("result", + xtag("t", t), + xtag("lo.tm", lo.first), + xtag("lo.value", lo_value), + xtag("log(lo.value/m)", log_lo_value), + xtag("m", this->scale_), + xtag("e", e), + xtag("retval", retval)); + + return retval; + } /*exterior_sample*/ + + TaggedRcptr + ExpProcess::self_tp() + { + return Reflect::make_rctp(this); + } /*self_tp*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end ExpProcess.cpp */ diff --git a/src/process/Realization.cpp b/src/process/Realization.cpp new file mode 100644 index 00000000..0697fc39 --- /dev/null +++ b/src/process/Realization.cpp @@ -0,0 +1,5 @@ +/* Realization.cpp */ + +#include "Realization.hpp" + +/* end Realization.cpp */ diff --git a/src/process/UpxEvent.cpp b/src/process/UpxEvent.cpp new file mode 100644 index 00000000..5faa97b8 --- /dev/null +++ b/src/process/UpxEvent.cpp @@ -0,0 +1,45 @@ +/* @file UpxEvent.cpp */ + +#include "UpxEvent.hpp" +#include "xo/reflect/StructReflector.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tag.hpp" + +namespace xo { + using xo::reflect::StructReflector; + using xo::tostr; + using xo::xtag; + + namespace process { + UpxEvent::UpxEvent() = default; + + void + UpxEvent::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + //REFLECT_MEMBER(sr, contents); + REFLECT_MEMBER(sr, tm); + REFLECT_MEMBER(sr, upx); + } + } /*reflect_self*/ + + void + UpxEvent::display(std::ostream & os) const + { + os << "tm()) + << xtag("x", this->upx()) + << ">"; + } /*display*/ + + std::string + UpxEvent::display_string() const { + return tostr(*this); + } /*display_string*/ + + } /*namespace process*/ +} /*namespace xo*/ + +/* end UpxEvent.cpp */ diff --git a/src/process/UpxToConsole.cpp b/src/process/UpxToConsole.cpp new file mode 100644 index 00000000..f78a3499 --- /dev/null +++ b/src/process/UpxToConsole.cpp @@ -0,0 +1,15 @@ +/* @file UpxToConsole.cpp */ + +#include "UpxToConsole.hpp" + +namespace xo { + namespace process { + ref::rp + UpxToConsole::make() + { + return new UpxToConsole(); + } /*make*/ + + UpxToConsole::UpxToConsole() = default; + } /*namespace process*/ +} /*namespace xo*/ diff --git a/src/process/init_process.cpp b/src/process/init_process.cpp new file mode 100644 index 00000000..fead726d --- /dev/null +++ b/src/process/init_process.cpp @@ -0,0 +1,40 @@ +/* file init_process.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "init_process.hpp" +#include "xo/printjson/init_printjson.hpp" + +#include "UpxEvent.hpp" +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + using xo::process::UpxEvent; + + void + InitSubsys::init() + { + UpxEvent::reflect_self(); + } /*init*/ + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* direct subsystem dependencies for process/ + * + * UpxEventStore --uses-> printjson (via reactor/EventStore.hpp) + */ + retval ^= InitSubsys::require(); + + /* process/'s own initialization code */ + retval ^= Subsystem::provide("process", &init); + + return retval; + } /*require*/ + +} /*namespace xo*/ + +/* end init_process.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..39bd3870 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,28 @@ +# build unittest 'process/unittest' + +# These tests can use the Catch2-provided main + +set(SELF_EXE utest.process) +set(SELF_SRCS + ProcessReflect.test.cpp + RealizationSource.test.cpp + process_utest_main.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +target_code_coverage(${SELF_EXE} AUTO ALL) + +# ---------------------------------------------------------------- +# internal dependencies (on this codebase) + +xo_self_dependency(${SELF_EXE} process) + +# ---------------------------------------------------------------- +# external dependencies + +xo_dependency(${SELF_EXE} simulator) +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# end CMakeLists.txt diff --git a/utest/ProcessReflect.test.cpp b/utest/ProcessReflect.test.cpp new file mode 100644 index 00000000..11ac995b --- /dev/null +++ b/utest/ProcessReflect.test.cpp @@ -0,0 +1,30 @@ +/* @file ProcessReflect.test.cpp */ + +#include "xo/process/init_process.hpp" +#include "xo/reflect/Reflect.hpp" +#include + +namespace xo { + using xo::reflect::TypeDescrBase; + + namespace ut { + static InitEvidence s_init = (InitSubsys::require()); + + TEST_CASE("process-reflect", "[reflect]") { + Subsystem::initialize_all(); + + char const * c_self = "TEST_CASE:process-reflect"; + constexpr bool c_logging_enabled = true; + + scope log(XO_DEBUG2(c_logging_enabled, c_self)); + + // this ought to work but doesn't (too much output?)... + //log && log(xo::reflect::reflected_types_printer()); + + xo::reflect::TypeDescrBase::print_reflected_types(std::cout); + } /*TEST_CASE(process-reflect)*/ + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end ProcessReflect.test.cpp */ diff --git a/utest/RealizationSource.test.cpp b/utest/RealizationSource.test.cpp new file mode 100644 index 00000000..5d8e98c1 --- /dev/null +++ b/utest/RealizationSource.test.cpp @@ -0,0 +1,261 @@ +/* @file RealizationSource.test.cpp */ + +//#include "time/Time.hpp" +#include "xo/process/RealizationSource.hpp" +#include "xo/process/LogNormalProcess.hpp" +#include "xo/process/BrownianMotion.hpp" +#include "xo/randomgen/xoshiro256.hpp" +#include "xo/simulator/Simulator.hpp" +#include "xo/indentlog/print/printer.hpp" +#include "xo/indentlog/scope.hpp" +#include + +namespace xo { + using xo::sim::Simulator; + using xo::process::RealizationSourceBase; + using xo::process::RealizationSource; + using xo::process::RealizationTracer; + using xo::process::LogNormalProcess; + using xo::process::ExpProcess; + using xo::process::BrownianMotion; + using xo::rng::xoshiro256ss; + using xo::reactor::SinkToConsole; + using xo::ref::rp; + using xo::time::timeutil; + using xo::time::seconds; + using xo::time::utc_nanos; + //using xo::print::printer; + using xo::scope; + using xo::xtag; + using std::chrono::hours; + using std::chrono::minutes; + + namespace ut { + /* TODO: move this to time/utest/ */ + TEST_CASE("time-formatting", "[time][print]") { + /* TODO: unit test for time conversions */ + + constexpr char const * c_self = "TEST_CASE:time-formatting"; + constexpr bool c_logging_enabled = true; + + utc_nanos t0 = timeutil::ymd_hms_usec(20220610 /*ymd*/, + 162905 /*hms*/, + 123456 /*usec*/); + + std::stringstream ss; + xo::timeutil::print_utc_ymd_hms_usec(t0, ss); + + REQUIRE(ss.str() == "20220610:16:29:05.123456"); + +#ifdef NOT_IN_USE + BrownianMotion bm = BrownianMotion::make(xxx t0, + xxx dev, + xxx seed); +#endif + } /*TEST_CASE(time-formatting)*/ + + /* TODO: move this to simulator/utest/ */ + TEST_CASE("empty-simulation", "[simulation][trivial]") { + constexpr char const * c_self = "TEST_CASE:empty-simulation"; + constexpr bool c_logging_enabled = true; + + /* arbitrary 'starting time' */ + utc_nanos t0 = timeutil::ymd_hms_usec(20220610 /*ymd*/, + 162905 /*hms*/, + 123456 /*usec*/); + + rp sim = Simulator::make(t0); + sim->set_loglevel(log_level::chatty); + + REQUIRE(sim->is_exhausted()); + + utc_nanos t1 = t0 + hours(1); + + sim->run_until(t1); + + REQUIRE((sim->is_exhausted() || (sim->next_tm() > t1))); + } /*TEST_CASE(empty-simulation)*/ + + /* test simulator with a single source */ + TEST_CASE("sim-brownian-motion", "[process][simulation]") { + constexpr char const * c_self = "TEST_CASE:sim-brownian-motion"; + + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG2(c_logging_enabled, c_self)); + + /* arbitrary 'starting time' */ + utc_nanos t0 = timeutil::ymd_hms_usec(20220610 /*ymd*/, + 162905 /*hms*/, + 123456 /*usec*/); + + rp sim = Simulator::make(t0); + sim->set_loglevel(c_logging_enabled + ? log_level::chatty + : log_level::error); + + REQUIRE(sim->is_exhausted()); + + log && log("create brownian motion process 'bm'.."); + + ref::rp> bm + = BrownianMotion::make(t0, + 0.30 /*sdev -- annualized volatility*/, + 12345678UL /*seed*/); + + log && log("..done"); + + + log && log("create realization tracer.."); + + rp> tracer + = RealizationTracer::make(bm); + + log && log("..done"); + + std::vector> sample_v; + + auto sink + = ([&sample_v] + (std::pair const & ev) + { sample_v.push_back(ev); }); + + log && log("create sim source from tracer.."); + + /* what is step dt? */ + rp, double, decltype(sink)>> + sim_source + = RealizationSourceBase, double, decltype(sink)>::make(tracer, + std::chrono::seconds(1) /*ev_interval_dt*/, + sink); + + log && log("..done"); + + log && log("add sim source to simulator.."); + + sim->add_source(sim_source); + + log&& log("..done"); + + utc_nanos t1 = t0 + minutes(1); + + log && log("run sim.."); + + sim->run_until(t1); + + log && log("..done"); + + log && log("verify sample_v.."); + + /* 1-minute simulation with 1-second samples */ + REQUIRE(sample_v.size() == 61); + + utc_nanos sample_t0 = sample_v[0].first; + + for(size_t i = 0; i < sample_v.size(); ++i) { + REQUIRE(sample_v[i].first == t0 + seconds(i)); + } + + log && log("..done"); + + //lscope.log(xtag("sample_v.size", sample_v.size())); + + } /*TEST_CASE("sim-brownian-motion")*/ + + TEST_CASE("sim-brownian-motion-with-sink", "[process][simulation]") { + constexpr char const * c_self = "TEST_CASE:sim-brownian-motion-with-sink"; + constexpr bool c_logging_enabled = false; + + scope log(XO_DEBUG2(c_logging_enabled, c_self)); + + utc_nanos t0 = timeutil::ymd_hms_usec(20220718 /*ymd*/, + 120000 /*hms*/, + 0 /*usec*/); + + auto bm + = BrownianMotion::make(t0, + 0.50 /*annualized volatility*/, + 65431123UL /*seed*/); + + auto tracer + = RealizationTracer::make(bm); + + auto realization + = RealizationSource, double>::make(tracer, + std::chrono::seconds(1) /*ev_interval_dt*/); + + rp>> sink + = new SinkToConsole>(); + + realization->attach_sink(sink); + } /*TEST_CASE(sim-brownian-motion-with-sink)*/ + + TEST_CASE("sim-lognormal", "[process][simulation]") { + constexpr char const * c_self = "TEST_CASE:sim-lognormal"; + constexpr bool c_logging_enabled = false; + + scope log(XO_LITERAL(log_level::never, c_self, "")); + + /* arbitrary 'starting time' */ + utc_nanos t0 = timeutil::ymd_hms_usec(20220610 /*ymd*/, + 162905 /*hms*/, + 123456 /*usec*/); + + rp sim = Simulator::make(t0); + sim->set_loglevel(c_logging_enabled + ? log_level::chatty + : log_level::error); + + REQUIRE(sim->is_exhausted()); + + rp ebm + (LogNormalProcess::make + (t0, + 1.0 /*x0*/, + 0.30 /*sdev -- annualized volatility*/, + 12345678UL /*seed*/)); + + /* recover the exponentiated process, for testing */ + //StochasticProcess * bm = ebm->exponent_process(); + + rp> tracer + = RealizationTracer::make(ebm.get()); + + /* will be: samples from log-normal brownian motion */ + std::vector> sample_v; + + /* collect process samples as sim runs */ + auto sink + = ([&sample_v] + (std::pair const & ev) + { sample_v.push_back(ev); }); + + rp, double, decltype(sink)>> + sim_source + = RealizationSourceBase, double, decltype(sink)>::make(tracer, + std::chrono::seconds(1) /*ev_interval_dt*/, + sink); + + sim->add_source(sim_source); + + utc_nanos t1 = t0 + minutes(1); + + sim->run_until(t1); + + /* 1-minute simulation with 1-second samples */ + REQUIRE(sample_v.size() == 61); + + utc_nanos sample_t0 = sample_v[0].first; + + for(size_t i = 0; i < sample_v.size(); ++i) { + REQUIRE(sample_v[i].first == t0 + seconds(i)); + /* exponentiated process will have strictly +ve values */ + REQUIRE(sample_v[i].second > 0.0); + } + + log && log(xtag("sample_v.size", sample_v.size())); + } /*TEST_CASE("sim-lognormal")*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end RealizationSource.test.cpp */ diff --git a/utest/process_utest_main.cpp b/utest/process_utest_main.cpp new file mode 100644 index 00000000..b1338709 --- /dev/null +++ b/utest/process_utest_main.cpp @@ -0,0 +1,6 @@ +/* @file process_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end process_utest_main.cpp */ From f6a1fe7dc07bc49dad4b1310d2c6af2e12ed0d29 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:07:00 -0400 Subject: [PATCH 0287/2524] + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..eff45bd2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +build*/* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json From dc723b7ce38d338f69db7baf6228f50f560776ce Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:08:37 -0400 Subject: [PATCH 0288/2524] github: + workflow --- .github/workflows/main.yml | 231 +++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..fd91d47f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,231 @@ +name: build xo-process + xo dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen + + # ---------------------------------------------------------------- + + - name: Clone reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/reactor + + - name: Configure reactor + # configure cmake for reactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor + + - name: Build reactor + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Install reactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reactor + + # ---------------------------------------------------------------- + + - name: Configure self (process) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_process -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (process) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_process --config ${{env.BUILD_TYPE}} + + - name: Test self (process) + working-directory: ${{github.workspace}}/build_process + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 6cb78eeb2fc2fde463d7cae5849d15f400fe9a89 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:12:12 -0400 Subject: [PATCH 0289/2524] github: + xo-simulator dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ README.md | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fd91d47f..888fce40 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -215,6 +215,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone simulator + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-simulator + path: repo/simulator + + - name: Configure simulator + # configure cmake for simulator in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_simulator -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/simulator + + - name: Build simulator + run: cmake --build ${{github.workspace}}/build_simulator --config ${{env.BUILD_TYPE}} + + - name: Install simulator + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_simulator + + # ---------------------------------------------------------------- + - name: Configure self (process) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type diff --git a/README.md b/README.md index 154de215..53d0b6d0 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ constructive, simulation-aware models for stochastic processes build+install these first -- xo-reactor [github.com/Rconybea/xo-reactor](https://github.com/Rconybea/xo-reactor) +- xo-simulator [github.com/Rconybea/xo-simulator](https://github.com/Rconybea/xo-simulator) - randomgen [github.com/Rconybea/randomgen](https://github.com/Rconybea/randomgen) # build + install From eca9fd9eab2f9708c885a02317259f882a0f6ade Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:19:43 -0400 Subject: [PATCH 0290/2524] github: + xo-ordinaltree dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 53837448..c5719ada 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -215,6 +215,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + - name: Clone reactor uses: actions/checkout@v3 with: From 69d4c89aa60cd8b101b5cb9b19b93d86736a6050 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:19:58 -0400 Subject: [PATCH 0291/2524] doc: + README.md --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..f62f480e --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# simulator library + +in-memory deterministic simulator + +## Getting Started + +### build + install dependencies + +build+install these first + +- xo-reactor [github.com/Rconybea/xo-reactor](https://github.com/Rconybea/xo-reactor) +- xo-ordinaltree [github.com/Rconybea/xo-ordinaltree](https://github.com/Rconybea/xo-ordinaltree) + +### build + install xo-simulator +``` +$ cd xo-simulator +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +### build for unit test coverage +``` +$ cd xo-simulator +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +## Development + +### LSP support + +LSP looks for compile commands in the root of the source tree; +cmake creates them in the root of its build directory. + +``` +$ cd xo-simulator +$ ln -s build/compile_commands.json +``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-simulator/build +$ cmake -LAH +``` From ee154327ec8631351cd2fbc21bbe0a666eab11e3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:21:42 -0400 Subject: [PATCH 0292/2524] github: + xo-ordinaltree dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 888fce40..618a85e4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -196,6 +196,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + - name: Clone reactor uses: actions/checkout@v3 with: From 64f6d92f0456054ccf6d6ab5615f2ad96ea3232e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:21:53 -0400 Subject: [PATCH 0293/2524] doc: README nit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 53d0b6d0..fe4f6263 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ build+install these first ## build ``` -$ cd process +$ cd xo-process $ mkdir build $ cd build $ INSTALL_PREFIX=/usr/local # or wherever you prefer From c96029fa1bcff13bfa46e4f034d4cb1e44650bdb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:31:38 -0400 Subject: [PATCH 0294/2524] initial implementation --- CMakeLists.txt | 44 +++++++++++++++++++++++ README.md | 46 ++++++++++++++++++++++++ cmake/xo_pyprintjsonConfig.cmake.in | 4 +++ include/xo/pyprintjson/pyprintjson.hpp | 25 +++++++++++++ src/pyprintjson/CMakeLists.txt | 8 +++++ src/pyprintjson/EXAMPLES | 2 ++ src/pyprintjson/pyprintjson.cpp | 50 ++++++++++++++++++++++++++ src/pyprintjson/pyprintjson.hpp.in | 25 +++++++++++++ 8 files changed, 204 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/xo_pyprintjsonConfig.cmake.in create mode 100644 include/xo/pyprintjson/pyprintjson.hpp create mode 100644 src/pyprintjson/CMakeLists.txt create mode 100644 src/pyprintjson/EXAMPLES create mode 100644 src/pyprintjson/pyprintjson.cpp create mode 100644 src/pyprintjson/pyprintjson.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..6f326e13 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,44 @@ +# xo-pyprintjson/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pyprintjson VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# 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 (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/pyprintjson) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/README.md b/README.md new file mode 100644 index 00000000..68fbdce3 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# python bindings for c++ printjson library (xo-printjson) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-printjson](https://github.com/Rconybea/xo-printjson) + +### build + install + +``` +$ cd xo-pyprintjson +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +### build for unit test coverage +``` +$ cd xo-pyprintjson +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pyprintjson +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` diff --git a/cmake/xo_pyprintjsonConfig.cmake.in b/cmake/xo_pyprintjsonConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pyprintjsonConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/pyprintjson/pyprintjson.hpp b/include/xo/pyprintjson/pyprintjson.hpp new file mode 100644 index 00000000..c5fa007d --- /dev/null +++ b/include/xo/pyprintjson/pyprintjson.hpp @@ -0,0 +1,25 @@ +/* @file pyprintjson.hpp + * + * automatically generated from src/pyprintjson/pyprintjson.hpp.in + * see src/pyprintjson/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYPRINTJSON_MODULE_NAME(), m) { ... } + */ +#define PYPRINTJSON_MODULE_NAME() pyprintjson + +/* example: + * py::module_::import(PYPRINTJSON_MODULE_NAME_STR) + */ +#define PYPRINTJSON_MODULE_NAME_STR "pyprintjson" + +/* example: + * PYPRINTJSON_IMPORT_MODULE() + * replaces + * py::module_::import("pyprintjson") + */ +#define PYPRINTJSON_IMPORT_MODULE() py::module_::import("pyprintjson") + +/* end pyprintjson.hpp */ diff --git a/src/pyprintjson/CMakeLists.txt b/src/pyprintjson/CMakeLists.txt new file mode 100644 index 00000000..5e77c995 --- /dev/null +++ b/src/pyprintjson/CMakeLists.txt @@ -0,0 +1,8 @@ +# xo_pyprintjson/src/pyprintjson/CMakeLists.txt + +set(SELF_LIB pyprintjson) +set(SELF_SRCS pyprintjson.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) + +xo_pybind11_dependency(${SELF_LIB} printjson) diff --git a/src/pyprintjson/EXAMPLES b/src/pyprintjson/EXAMPLES new file mode 100644 index 00000000..5965b9b6 --- /dev/null +++ b/src/pyprintjson/EXAMPLES @@ -0,0 +1,2 @@ +import pyprintjson +pj=pyprintjson.PrintJson() diff --git a/src/pyprintjson/pyprintjson.cpp b/src/pyprintjson/pyprintjson.cpp new file mode 100644 index 00000000..f49d4b41 --- /dev/null +++ b/src/pyprintjson/pyprintjson.cpp @@ -0,0 +1,50 @@ +/* @file pyprintjson.cpp */ + +// note: need pyreflect/ here bc pyreflect.hpp is generated, located in build directory +#include "pyprintjson.hpp" +#include "xo/pyreflect/pyreflect.hpp" + +#include "xo/printjson/PrintJson.hpp" +#include "xo/reflect/TaggedRcptr.hpp" +//#include "reflect/SelfTagging.hpp" +//#include "refcnt/Refcounted.hpp" +//#include "refcnt/Unowned.hpp" +#include "xo/pyutil/pyutil.hpp" +//#include +//#include +//#include +//#include + +namespace xo { + namespace py = pybind11; + + namespace json { + using xo::reflect::SelfTagging; + using xo::reflect::TaggedRcptr; + using xo::ref::rp; + using xo::ref::unowned_ptr; + + PYBIND11_MODULE(PYPRINTJSON_MODULE_NAME(), m) { + PYREFLECT_IMPORT_MODULE(); + + py::class_>(m, "PrintJson") + .def_static("instance", &PrintJsonSingleton::instance) + .def("print", + [](PrintJson & pj, TaggedRcptr p) + { + pj.print_tp(p, &std::cout); std::cout << "\n"; + }, + py::arg("value")) + .def("print", + [](PrintJson & pj, rp const & p) + { + pj.print_obj(p, &std::cout); std::cout << "\n"; + }, + py::arg("value")); + + //m.def("print_json", [](){ return PrintJsonSingleton::instance_ptr(); }); + } /*pyprintjson*/ + } /*namespace json*/ +} /*namespace xo*/ + +/* end pyprintjson.cpp */ diff --git a/src/pyprintjson/pyprintjson.hpp.in b/src/pyprintjson/pyprintjson.hpp.in new file mode 100644 index 00000000..d80e5aba --- /dev/null +++ b/src/pyprintjson/pyprintjson.hpp.in @@ -0,0 +1,25 @@ +/* @file pyprintjson.hpp + * + * automatically generated from src/pyprintjson/pyprintjson.hpp.in + * see src/pyprintjson/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYPRINTJSON_MODULE_NAME(), m) { ... } + */ +#define PYPRINTJSON_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(PYPRINTJSON_MODULE_NAME_STR) + */ +#define PYPRINTJSON_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * PYPRINTJSON_IMPORT_MODULE() + * replaces + * py::module_::import("pyprintjson") + */ +#define PYPRINTJSON_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pyprintjson.hpp */ From b7c2e1d15eded2aba2ff64e110ccdf2d0f82e96a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:32:03 -0400 Subject: [PATCH 0295/2524] + .gitignore --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8ea1f615 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# lsp keep state here +.cache +# typical build directories +build +ccov +# for lsp: manual symlink to chosen build directory +compile_commands.json From dc253cd388bc53218c01fc2f88fbebf984a61790 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:33:10 -0400 Subject: [PATCH 0296/2524] doc: minor README improvements --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 68fbdce3..34b77909 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ $ make install ``` (also see .github/workflows/main.yml) +## Development + ### build for unit test coverage ``` $ cd xo-pyprintjson @@ -44,3 +46,14 @@ while Cmake creates them in the root of its build directory. $ cd xo-pyprintjson $ ln -s build/compile_commands.json # supply compile commands to LSP ``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-pyprintjson/build +$ cmake -LAH +``` From a1f935579bf0674d974c3124ffb5d8647fbdc095 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:36:12 -0400 Subject: [PATCH 0297/2524] github: + workflow --- .github/workflows/main.yml | 155 +++++++++++++++++++++++++++++ include/xo/pyreflect/pyreflect.hpp | 25 +++++ 2 files changed, 180 insertions(+) create mode 100644 .github/workflows/main.yml create mode 100644 include/xo/pyreflect/pyreflect.hpp diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..289c7040 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,155 @@ +name: build xo-pyreflect + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/pyutil + + - name: Configure pyutil + # configure cmake for pyutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyutil + + - name: Build pyutil + run: cmake --build ${{github.workspace}}/build_pyutil --config ${{env.BUILD_TYPE}} + + - name: Install pyutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyutil + + # ---------------------------------------------------------------- + + - name: Configure self (pyreflect) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_pyreflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (pyreflect) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_pyreflect --config ${{env.BUILD_TYPE}} + + - name: Test self (pyreflect) + working-directory: ${{github.workspace}}/build_pyreflect + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} diff --git a/include/xo/pyreflect/pyreflect.hpp b/include/xo/pyreflect/pyreflect.hpp new file mode 100644 index 00000000..bbaef729 --- /dev/null +++ b/include/xo/pyreflect/pyreflect.hpp @@ -0,0 +1,25 @@ +/* @file pyreflect.hpp + * + * automatically generated from src/xo_pyreflect/pyreflect.hpp.in + * see src/xo_pyreflect/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYREFLECT_MODULE_NAME(), m) { ... } + */ +#define PYREFLECT_MODULE_NAME() pyreflect + +/* example: + * py::module_::import(PYREFLECT_MODULE_NAME_STR) + */ +#define PYREFLECT_MODULE_NAME_STR "pyreflect" + +/* example: + * PYREFLECT_IMPORT_MODULE() + * replaces + * py::module_::import("pyreflect") + */ +#define PYREFLECT_IMPORT_MODULE() py::module_::import("pyreflect") + +/* end pyreflect.hpp */ From 758b883f0e73c257529984e679925427200b4ba4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:36:40 -0400 Subject: [PATCH 0298/2524] bugfix: remove generated .hpp file --- include/xo/pyreflect/pyreflect.hpp | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 include/xo/pyreflect/pyreflect.hpp diff --git a/include/xo/pyreflect/pyreflect.hpp b/include/xo/pyreflect/pyreflect.hpp deleted file mode 100644 index bbaef729..00000000 --- a/include/xo/pyreflect/pyreflect.hpp +++ /dev/null @@ -1,25 +0,0 @@ -/* @file pyreflect.hpp - * - * automatically generated from src/xo_pyreflect/pyreflect.hpp.in - * see src/xo_pyreflect/CMakeLists.txt - */ - -/* python requires module name = library name - * example: - * PYBIND11_MODULE(PYREFLECT_MODULE_NAME(), m) { ... } - */ -#define PYREFLECT_MODULE_NAME() pyreflect - -/* example: - * py::module_::import(PYREFLECT_MODULE_NAME_STR) - */ -#define PYREFLECT_MODULE_NAME_STR "pyreflect" - -/* example: - * PYREFLECT_IMPORT_MODULE() - * replaces - * py::module_::import("pyreflect") - */ -#define PYREFLECT_IMPORT_MODULE() py::module_::import("pyreflect") - -/* end pyreflect.hpp */ From 711b0a580f74d3f69606f801a056a300b80852b8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:45:47 -0400 Subject: [PATCH 0299/2524] github: install pybind11-dev dep --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 289c7040..5d9c379b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,6 +25,9 @@ jobs: # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] run: sudo apt-get install -y catch2 + - name: Install pybind11-dev + run: sudo apt-get install -y pybind11-dev + # ---------------------------------------------------------------- - name: Clone xo-cmake From 5f927717b67aa1521cd888a6d4ab391c88f6df41 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:49:06 -0400 Subject: [PATCH 0300/2524] bugfix: compile fix to include path for generated pyreflect.hpp --- src/pyreflect/pyreflect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index 0b638658..c9b7f47a 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -1,7 +1,7 @@ /* @file pyreflect.cpp */ // note: need pyreflect/ here bc pyreflect.hpp is generated, located in build directory -#include "src/pyreflect/pyreflect.hpp" +#include "xo/pyreflect/pyreflect.hpp" #include "xo/reflect/TypeDescr.hpp" #include "xo/reflect/TaggedRcptr.hpp" #include "xo/reflect/SelfTagging.hpp" From c4ed2e7f22fa983913122ac31d936a3f8e8e277a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 12:54:16 -0400 Subject: [PATCH 0301/2524] github: + workflow --- .github/workflows/main.yml | 177 +++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..eb51827e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,177 @@ +name: build xo-pyprintjson + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + - name: Install pybind11-dev + run: sudo apt-get install -y pybind11-dev + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + + - name: Clone pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/pyutil + + - name: Configure pyutil + # configure cmake for pyutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyutil + + - name: Build pyutil + run: cmake --build ${{github.workspace}}/build_pyutil --config ${{env.BUILD_TYPE}} + + - name: Install pyutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyutil + + # ---------------------------------------------------------------- + + - name: Configure self (pyprintjson) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_pyprintjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (pyprintjson) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_pyprintjson --config ${{env.BUILD_TYPE}} + + - name: Test self (pyprintjson) + working-directory: ${{github.workspace}}/build_pyprintjson + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 70c76bd4a85e4b7bd6dd49c12ce759a68d6ffc13 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 13:29:42 -0400 Subject: [PATCH 0302/2524] xo-cmake: generate .hpp in build directory (src not writable w/ nix) --- cmake/xo_cxx.cmake | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 6a402cca..478cb80b 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -377,8 +377,15 @@ endmacro() # 2. pyfoo/pyfoo.hpp.in -> pyfoo/pyfoo.hpp # macro(xo_pybind11_library target projectTargets source_files) - configure_file(${target}.hpp.in - ${PROJECT_SOURCE_DIR}/include/xo/${target}/${target}.hpp) + configure_file( + ${target}.hpp.in + ${PROJECT_BINARY_DIR}/${target}.hpp) + # was ${PROJECT_SOURCE_DIR}/include/xo/${target}/${target}.hpp) + + install( + FILES ${PROJECT_BINARY_DIR}/${target}.hpp + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${target}) # find_package(Python..) finds python in # /Library/Frameworks/Python.framework/... From 36e012934856062dfb8c6f5511ec00dcfcc01819 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 13:34:39 -0400 Subject: [PATCH 0303/2524] github: + pyreflect dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index eb51827e..af5526f6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -161,6 +161,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone pyreflect + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyreflect + path: repo/pyreflect + + - name: Configure pyreflect + # configure cmake for pyreflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyreflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyreflect + + - name: Build pyreflect + run: cmake --build ${{github.workspace}}/build_pyreflect --config ${{env.BUILD_TYPE}} + + - name: Install pyreflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyreflect + + # ---------------------------------------------------------------- + - name: Configure self (pyprintjson) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 10856b2ed9c55633a6b28238b6490221f3cd7785 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 13:39:08 -0400 Subject: [PATCH 0304/2524] bugfix: track modified path to generated include pyreflect.hpp --- src/pyreflect/pyreflect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index c9b7f47a..fc6e8fe8 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -1,7 +1,7 @@ /* @file pyreflect.cpp */ // note: need pyreflect/ here bc pyreflect.hpp is generated, located in build directory -#include "xo/pyreflect/pyreflect.hpp" +#include "pyreflect.hpp" #include "xo/reflect/TypeDescr.hpp" #include "xo/reflect/TaggedRcptr.hpp" #include "xo/reflect/SelfTagging.hpp" From 63d63f953f1cdcbb6ddab27ca181973721a953d1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 15:53:41 -0400 Subject: [PATCH 0305/2524] + include/README.md so git checkout creates dir --- include/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 include/README.md diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..4454f162 --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future pyreflect #include files From 42cd9aedca5834577d122a6f2eba3dcd0730c7d6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 16:06:09 -0400 Subject: [PATCH 0306/2524] initial implementation --- CMakeLists.txt | 43 +++++++++++++++++++++++++++++++ EXAMPLES | 9 +++++++ cmake/xo_pywebutilConfig.cmake.in | 4 +++ include/README.md | 1 + src/pywebutil/CMakeLists.txt | 7 +++++ src/pywebutil/pywebutil.cpp | 34 ++++++++++++++++++++++++ src/pywebutil/pywebutil.hpp.in | 25 ++++++++++++++++++ 7 files changed, 123 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 EXAMPLES create mode 100644 cmake/xo_pywebutilConfig.cmake.in create mode 100644 include/README.md create mode 100644 src/pywebutil/CMakeLists.txt create mode 100644 src/pywebutil/pywebutil.cpp create mode 100644 src/pywebutil/pywebutil.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..ed35dc91 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,43 @@ +# xo-pywebutil/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pywebutil VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# 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 (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/pywebutil) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/EXAMPLES b/EXAMPLES new file mode 100644 index 00000000..a495d316 --- /dev/null +++ b/EXAMPLES @@ -0,0 +1,9 @@ +Creating this pybind11 library to hold low-dependency wrappers +used for interaction between {websock/, pywebsock/} and other subsystems. + +1. This library needs to be a (runtime) dependency of pyxxx libraries that + use reactor::EventStore, for example pyprocess/, to supply wrappers + for EndpointDescr and Alist. + +2. If we chose to put this code in pywebsock/, then pywebsock + libwebsocket + would become runtime dependencies of libraries like pyprocess/. diff --git a/cmake/xo_pywebutilConfig.cmake.in b/cmake/xo_pywebutilConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pywebutilConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..3df846f2 --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future pywebutil #include files diff --git a/src/pywebutil/CMakeLists.txt b/src/pywebutil/CMakeLists.txt new file mode 100644 index 00000000..0a8dc225 --- /dev/null +++ b/src/pywebutil/CMakeLists.txt @@ -0,0 +1,7 @@ +# xo-pywebutil/src/pywebutil/CMakeLists.txt + +set(SELF_LIB pywebutil) +set(SELF_SRCS pywebutil.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} webutil) diff --git a/src/pywebutil/pywebutil.cpp b/src/pywebutil/pywebutil.cpp new file mode 100644 index 00000000..dbaa8ef6 --- /dev/null +++ b/src/pywebutil/pywebutil.cpp @@ -0,0 +1,34 @@ +/* @file pywebutil.cpp */ + +#include "pywebutil.hpp" +#include "xo/webutil/HttpEndpointDescr.hpp" +#include "xo/webutil/StreamEndpointDescr.hpp" +#include "xo/pyutil/pyutil.hpp" +#include + +namespace xo { + //using xo::web::Alist; + using xo::web::HttpEndpointDescr; + using xo::ref::rp; + //using xo::time::utc_nanos; + namespace py = pybind11; + + namespace web { + PYBIND11_MODULE(PYWEBUTIL_MODULE_NAME(), m) { + //PYxxx_IMPORT_MODULE(); + + /* module docstring */ + m.doc() = "pybind11 plugin for xo.web_util"; + + py::class_(m, "EndpointDescr") + .def_property_readonly("uri_pattern", &HttpEndpointDescr::uri_pattern) + .def("__repr__", &HttpEndpointDescr::display_string); + + py::class_(m, "StreamEndpointDescr") + .def_property_readonly("uri_pattern", &StreamEndpointDescr::uri_pattern) + .def("__repr__", &StreamEndpointDescr::display_string); + } /*web*/ + } /*namespace web*/ +} /*namespace xo*/ + +/* end pywebutil.cpp */ diff --git a/src/pywebutil/pywebutil.hpp.in b/src/pywebutil/pywebutil.hpp.in new file mode 100644 index 00000000..086701c1 --- /dev/null +++ b/src/pywebutil/pywebutil.hpp.in @@ -0,0 +1,25 @@ +/* @file pywebutil.hpp + * + * automatically generated from src/pywebutil/pywebutil.hpp.in + * see src/pywebutil/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYWEBUTIL_MODULE_NAME(), m) { ... } + */ +#define PYWEBUTIL_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(PYWEBUTIL_MODULE_NAME_STR) + */ +#define PYWEBUTIL_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * PYWEBUTIL_IMPORT_MODULE() + * replaces + * py::module_::import("pywebutil") + */ +#define PYWEBUTIL_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pywebutil.hpp */ From 368b65465585dfbdbfac9a3f63fbc205eb7b4ccf Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 16:06:41 -0400 Subject: [PATCH 0307/2524] + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f52f1311 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# lsp keeps state here +.cache +# typical build directory +build +# lsp: symlink to file in build directory (established manually) +compile_commands.json From c3bc9f1b2e6c5cc46987c976fadfc08e6d964f20 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 16:12:48 -0400 Subject: [PATCH 0308/2524] github: + workflow --- .github/workflows/main.yml | 177 +++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..0b2676ed --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,177 @@ +name: build xo-pywebutil + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + - name: Install pybind11-dev + run: sudo apt-get install -y pybind11-dev + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + +# # ---------------------------------------------------------------- +# +# - name: Clone subsys +# uses: actions/checkout@v3 +# with: +# repository: Rconybea/subsys +# path: repo/subsys +# +# - name: Configure subsys +# # configure cmake for subsys in dedicated build directory. +# run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys +# +# - name: Build subsys +# run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} +# +# - name: Install subsys +# # install into ${{github.workspace}}/local +# run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + + - name: Clone pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/pyutil + + - name: Configure pyutil + # configure cmake for pyutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyutil + + - name: Build pyutil + run: cmake --build ${{github.workspace}}/build_pyutil --config ${{env.BUILD_TYPE}} + + - name: Install pyutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyutil + + # ---------------------------------------------------------------- + + - name: Configure self (pywebutil) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_pywebutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (pywebutil) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_pywebutil --config ${{env.BUILD_TYPE}} + + - name: Test self (pywebutil) + working-directory: ${{github.workspace}}/build_pywebutil + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 0b5fffc1208327a2162a71ba6f6325b7df60c545 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 16:13:56 -0400 Subject: [PATCH 0309/2524] doc: + README.md --- README.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..293fa8a4 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# python bindings for c++ webutil library (xo-webutil) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-webutil](https://github.com/Rconybea/xo-webutil) + +### build + install + +``` +$ cd xo-pywebutil +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +## Development + +### build for unit test coverage +``` +$ cd xo-pywebutil +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pywebutil +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-pywebutil/build +$ cmake -LAH +``` From 3487e3780cedf9f13589e6da53089ba9e76ec18d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:01:49 -0400 Subject: [PATCH 0310/2524] initial implementation --- CMakeLists.txt | 44 ++++++++++ README.md | 61 +++++++++++++ cmake/xo_pyreactorConfig.cmake.in | 4 + include/README.md | 1 + src/pyreactor/CMakeLists.txt | 8 ++ src/pyreactor/pyreactor.cpp | 141 ++++++++++++++++++++++++++++++ src/pyreactor/pyreactor.hpp.in | 25 ++++++ 7 files changed, 284 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/xo_pyreactorConfig.cmake.in create mode 100644 include/README.md create mode 100644 src/pyreactor/CMakeLists.txt create mode 100644 src/pyreactor/pyreactor.cpp create mode 100644 src/pyreactor/pyreactor.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..aec8d379 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,44 @@ +# xo-pyreactor/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pyreactor VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# 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 (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/pyreactor) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/README.md b/README.md new file mode 100644 index 00000000..f618c641 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# python bindings for c++ reactor library (xo-reactor) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-reactor](https://github.com/Rconybea/xo-reactor) +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-pyreflect](https://github.com/Rconybea/xo-pyreflect) +- [github/Rconybea/xo-pyprintjson](https://github.com/Rconybea/xo-pyprintjson) + +### build + install + +``` +$ cd xo-pyreactor +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +## Development + +### build for unit test coverage +``` +$ cd xo-pyreactor +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pyreactor +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-pyreactor/build +$ cmake -LAH +``` diff --git a/cmake/xo_pyreactorConfig.cmake.in b/cmake/xo_pyreactorConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pyreactorConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..4a0ad1c1 --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future pyreactor #include files diff --git a/src/pyreactor/CMakeLists.txt b/src/pyreactor/CMakeLists.txt new file mode 100644 index 00000000..ad752e96 --- /dev/null +++ b/src/pyreactor/CMakeLists.txt @@ -0,0 +1,8 @@ +# xo_pyreactor/src/pyreactor/CMakeLists.txt + +set(SELF_LIB pyreactor) +set(SELF_SRCS pyreactor.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) + +xo_pybind11_dependency(${SELF_LIB} reactor) diff --git a/src/pyreactor/pyreactor.cpp b/src/pyreactor/pyreactor.cpp new file mode 100644 index 00000000..5f8cf77b --- /dev/null +++ b/src/pyreactor/pyreactor.cpp @@ -0,0 +1,141 @@ +/* @file ReactorPy.cpp */ + +#include "pyreactor.hpp" +#include "xo/pyprintjson/pyprintjson.hpp" +#include "xo/pyreflect/pyreflect.hpp" + +#include "xo/reactor/Reactor.hpp" +#include "xo/reactor/ReactorSource.hpp" +#include "xo/reactor/EventStore.hpp" +#include "xo/reactor/Sink.hpp" +#include "xo/webutil/StreamEndpointDescr.hpp" +//#include "time/Time.hpp" + +//#include "xo/pyutil/pytime.hpp" +#include "xo/pyutil/pyutil.hpp" + +//#include +//#include +#include + +namespace xo { + using xo::json::PrintJsonSingleton; + using xo::fn::CallbackId; + using xo::ref::Refcount; + using xo::ref::rp; + using xo::time::utc_nanos; + using xo::tostr; + namespace py = pybind11; + + namespace reactor { + PYBIND11_MODULE(PYREACTOR_MODULE_NAME(), m) { + /* e.g. for TypeDescr */ + PYREFLECT_IMPORT_MODULE(); //py::module_::import("pyreflect"); + PYPRINTJSON_IMPORT_MODULE(); //py::module_::import("pyprintjson"); + + /* module docstring */ + m.doc() = "pybind11 plugin for xo.reactor"; + + m.def("time2str", [](utc_nanos tm) { return tostr(tm); }); + + /* TODO: if we write pycallback/, then CallbackId wrapper belongs there */ + py::class_(m, "CallbackId"); + + py::class_>(m, "AbstractEventProcessor") + .def_property("name", + &AbstractEventProcessor::name, + &AbstractEventProcessor::set_name) + .def("reference_counter", [](AbstractEventProcessor const & x) { return x.reference_counter(); }) + .def("memory_address", [](AbstractEventProcessor const & x) { return (void*)&x; }) + .def("map_network", [](AbstractEventProcessor & x) { return AbstractEventProcessor::map_network(&x); }) + .def("__repr__", &AbstractEventProcessor::display_string); + + py::class_>(m, "AbstractSource") + .def_property_readonly("source_ev_type", &AbstractSource::source_ev_type) + .def_property_readonly("is_volatile", &AbstractSource::is_volatile) + .def_property_readonly("n_out_ev", &AbstractSource::n_out_ev) + .def_property_readonly("n_queued_out_ev", &AbstractSource::n_queued_out_ev) + .def("attach_sink", &AbstractSource::attach_sink) + .def("detach_sink", &AbstractSource::detach_sink) + /* editor bait: websock_endpoint_descr */ + .def("stream_endpoint_descr", &AbstractSource::stream_endpoint_descr) + .def("deliver_one", &AbstractSource::deliver_one) + .def("deliver_n", &AbstractSource::deliver_n, + py::arg("n")); + + py::class_>(m, "AbstractSink") + //.cdef("__repr__", &AbstractSink::display_string) + .def_property_readonly("sink_ev_type", &AbstractSink::sink_ev_type) + .def_property_readonly("n_in_ev", &AbstractSink::n_in_ev) + .def("attach_source", &AbstractSink::attach_source); + + py::class_> + (m, "ReactorSource") + .def_property_readonly("is_empty", &ReactorSource::is_empty) + .def_property_readonly("is_nonempty", &ReactorSource::is_nonempty) + .def_property_readonly("is_exhausted", &ReactorSource::is_exhausted) + .def_property_readonly("sim_current_tm", &ReactorSource::sim_current_tm) + .def_property("debug_sim_flag", + &ReactorSource::debug_sim_flag, + &ReactorSource::set_debug_sim_flag); + + py::class_> + (m, "AbstractEventStore") + .def_property_readonly("empty", &AbstractEventStore::empty) + .def_property_readonly("size", &AbstractEventStore::size) + .def("http_snapshot", + [](AbstractEventStore & self) { + std::stringstream ss; + self.http_snapshot(PrintJsonSingleton::instance(), &ss); + return ss.str(); + }) + .def("http_endpoint_descr", + [](AbstractEventStore & self, std::string const & url_prefix) { + return self.http_endpoint_descr(PrintJsonSingleton::instance(), url_prefix); + }, + py::arg("url_prefix")) + .def("clear", + &AbstractEventStore::clear); + + py::class_> + (m, "Reactor") + .def("add_source", + [](Reactor & self, rp src) { + return self.add_source(src.borrow()); + }) + .def("remove_source", + [](Reactor & self, rp src) { + return self.remove_source(src.borrow()); + }) + .def("run_one", &Reactor::run_one) + .def("run_n", &Reactor::run_n, py::arg("n")); + +#ifdef NOT_IN_USE // trying removed code in ProcessPy.cpp instead for now + /* prints + * std::pair + * pairs + */ + m.def("make_realization_printer", + [] + { + return new SinkToConsole>(); + }); + + py::class_>, + AbstractSink, + xo::ref::rp>>> + (m, "SinkToConsole"); +#endif + } /*pyreactor*/ + } /*namespace reactor*/ +} /*namespace xo*/ + +/* end ReactorPy.cpp */ diff --git a/src/pyreactor/pyreactor.hpp.in b/src/pyreactor/pyreactor.hpp.in new file mode 100644 index 00000000..edbb2e78 --- /dev/null +++ b/src/pyreactor/pyreactor.hpp.in @@ -0,0 +1,25 @@ +/* @file pyreactor.hpp + * + * automatically generated from src/pyreflect/pyreactor.hpp.in + * see src/pyreactor/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYREACTOR_MODULE_NAME(), m) { ... } + */ +#define PYREACTOR_MODULE_NAME() @SELF_LIBRARY_NAME@ + +/* example: + * py::module_::import(PYREACTOR_MODULE_NAME_STR) + */ +#define PYREACTOR_MODULE_NAME_STR "@SELF_LIBRARY_NAME@" + +/* example: + * PYREACTOR_IMPORT_MODULE() + * replaces + * py::module_::import("pyreactor") + */ +#define PYREACTOR_IMPORT_MODULE() py::module_::import("@SELF_LIBRARY_NAME@") + +/* end pyreactor.hpp */ From cc1c42b8c35c3a724d933551d34669e79e67af6c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:02:09 -0400 Subject: [PATCH 0311/2524] + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f52f1311 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# lsp keeps state here +.cache +# typical build directory +build +# lsp: symlink to file in build directory (established manually) +compile_commands.json From 0e9ecec2ed83c94bd3a3cb4a638d724e0a6d1185 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:10:23 -0400 Subject: [PATCH 0312/2524] initial implementation --- CMakeLists.txt | 44 +++++++ README.md | 48 ++++++++ cmake/cmake | 4 + cmake/xo_pyprocessConfig.cmake.in | 4 + include/README.md | 1 + src/pyprocess/CMakeLists.txt | 8 ++ src/pyprocess/pyprocess.cpp | 183 ++++++++++++++++++++++++++++++ src/pyprocess/pyprocess.hpp.in | 25 ++++ 8 files changed, 317 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/cmake create mode 100644 cmake/xo_pyprocessConfig.cmake.in create mode 100644 include/README.md create mode 100644 src/pyprocess/CMakeLists.txt create mode 100644 src/pyprocess/pyprocess.cpp create mode 100644 src/pyprocess/pyprocess.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..af0bae5b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,44 @@ +# xo-pyprocess/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pyprocess VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# 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 (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/pyprocess) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/README.md b/README.md new file mode 100644 index 00000000..2ad68ffb --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# python bindings for c++ stochastic process library (xo-process) + +# build + install +``` +$ cd xo-pyprocess +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +# build for unit test coverage +``` +$ cd xo-pyprocess +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +# LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pyprocess +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +# Examples + +Assumes `xo-pyprocess` installed to `~/local2/lib` + +``` +PYTHONPATH=~/local2/lib python +>>> import pyprocess +>>> dir(pyprocess) +``` diff --git a/cmake/cmake b/cmake/cmake new file mode 100644 index 00000000..b49b4828 --- /dev/null +++ b/cmake/cmake @@ -0,0 +1,4 @@ + /home/roland/proj/xo-pyprocess/cmake: + drwxr-xr-x 2 roland roland 4096 Oct 12 21:49 . + drwxr-xr-x 6 roland roland 4096 Oct 12 21:49 .. + -rw-r--r-- 1 roland roland 125 Oct 12 21:49 xo_pyprocessConfig.cmake.in diff --git a/cmake/xo_pyprocessConfig.cmake.in b/cmake/xo_pyprocessConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pyprocessConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..aca8853c --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future pyprocess #include files diff --git a/src/pyprocess/CMakeLists.txt b/src/pyprocess/CMakeLists.txt new file mode 100644 index 00000000..1b6c5347 --- /dev/null +++ b/src/pyprocess/CMakeLists.txt @@ -0,0 +1,8 @@ +# xo_pyprocess/src/pyprocess/CMakeLists.txt + +set(SELF_LIB pyprocess) +set(SELF_SRCS pyprocess.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) + +xo_pybind11_dependency(${SELF_LIB} process) diff --git a/src/pyprocess/pyprocess.cpp b/src/pyprocess/pyprocess.cpp new file mode 100644 index 00000000..21c2f287 --- /dev/null +++ b/src/pyprocess/pyprocess.cpp @@ -0,0 +1,183 @@ +/* @file pyprocess.cpp */ + +// note: need pyprocess/ here bc pyprocess.hpp is generated, located in build directory +#include "src/pyprocess/pyprocess.hpp" +#include "xo/pywebutil/pywebutil.hpp" +#include "xo/process/init_process.hpp" +#include "xo/process/UpxToConsole.hpp" +#include "xo/process/StochasticProcess.hpp" +#include "xo/process/BrownianMotion.hpp" +#include "xo/process/ExpProcess.hpp" +#include "xo/process/RealizationSource.hpp" +#include "xo/pyreactor/pyreactor.hpp" +#include "xo/reactor/EventStore.hpp" +#include "xo/reactor/PolyAdapterSink.hpp" +#include "xo/randomgen/random_seed.hpp" +#include "xo/randomgen/xoshiro256.hpp" +#include +#include +#include + +/* xo::ref::intrusive_ptr is an intrusively-reference-counted pointer. + * always safe to create one from a T* p + * (since refcount is directly accessible from p) + */ +PYBIND11_DECLARE_HOLDER_TYPE(T, xo::ref::intrusive_ptr, true); + +namespace xo { + using xo::reactor::AbstractSink; + using xo::reactor::AbstractEventStore; + using xo::reactor::StructEventStore; + using xo::reactor::PolyAdapterSink; + using xo::json::PrintJsonSingleton; + using xo::time::utc_nanos; + using xo::rng::Seed; + using xo::rng::xoshiro256ss; + using xo::ref::rp; + namespace py = pybind11; + + namespace process { + PYBIND11_MODULE(PYPROCESS_MODULE_NAME(), m) { + /* ensure process/ will be initialized */ + InitSubsys::require(); + /* ..and immediately perform init steps */ + Subsystem::initialize_all(); + + /* e.g. py wrapper for xo::reactor::ReactorSource */ + PYREACTOR_IMPORT_MODULE(); + /* e.g. py wrapper for xo::web::EndpointDescr */ + PYWEBUTIL_IMPORT_MODULE(); + + m.doc() = "pybind11 plugin for xo.process"; + + m.def("make_brownian_motion", + [](utc_nanos start_tm, + double annual_volatility) { + Seed seed; + + return BrownianMotion::make(start_tm, + annual_volatility, + seed); + }, + "create new BrownianMotion instance"); + + m.def("make_exponential_brownian_motion", + [](utc_nanos start_tm, + double start_value, + double annual_volatility) { + Seed seed; + + return ExpProcess::make(start_value /*scale*/, + BrownianMotion::make(start_tm, + annual_volatility, + seed)); + }, + py::arg("start_tm"), py::arg("start_value"), py::arg("annual_volatility")); + + py::class_, + xo::ref::rp>>(m, "StochasticProcess") + .def_property_readonly("t0", &StochasticProcess::t0) + .def_property_readonly("t0_value", &StochasticProcess::t0_value) + .def("exterior_sample", &StochasticProcess::exterior_sample) + .def("__repr__", &StochasticProcess::display_string); + + py::class_, + StochasticProcess, + xo::ref::rp>>(m, "BrownianMotion"); + //.def("exterior_sample", &BrownianMotion::exterior_sample) + //.def("__repr__", &BrownianMotion::display_string); + + py::class_, + xo::ref::rp>(m, "ExpProcess") + .def_property_readonly("exponent_process", + [](ExpProcess & self) { + return self.exponent_process().promote(); + }); + + m.def("make_tracer", + &RealizationTracer::make); + + py::class_, + xo::ref::rp>>(m, "RealizationTracer"); + + /* e.g. + * import datetime as dt + * t0=dt.datetime.now() + * ebm=pyprocess.make_exponential_brownian_motion(t0, 0.5) + * s=pyprocess.make_realization_source(ebm, dt.timedelta(seconds=1)) + */ + m.def("make_realization_source", + [](xo::ref::rp> p, + xo::time::nanos sample_dt) + { + auto tracer = RealizationTracer::make(p); + + return RealizationSource::make(tracer, + sample_dt); + }); + + /* note: providing __repr__ changes printing behavior, + * but uses default printer for inherited std::pair<..> + */ + py::class_(m, "UpxEvent") + .def_property_readonly("tm", &UpxEvent::tm) + .def_property_readonly("upx", &UpxEvent::upx) + .def("__repr__", &UpxEvent::display_string); + + py::class_, + reactor::ReactorSource, + xo::ref::rp>>(m, "RealizationSource") + .def_property_readonly("current_ev", &RealizationSource::current_ev); + + py::class_> + (m, "UpxToConsole"); + + using UpxEventStore = StructEventStore; + + /* see also: KalmanFilterStateEventStore in [pyfilter/pyfilter.cpp] + */ + py::class_> + (m, "UpxEventStore") + .def_static("make", &UpxEventStore::make) + .def_property_readonly("empty", &UpxEventStore::empty) + .def_property_readonly("size", &UpxEventStore::size) + .def("last_n", &UpxEventStore::last_n, py::arg("n")) + .def("last_dt", &UpxEventStore::last_dt, py::arg("dt")); + //.def("__repr__", &UpxEventStore::display_string); + + /* temporary -- to reveal compiler errors */ + using UpxAdapterSink = PolyAdapterSink; + + py::class_>(m, "UpxAdapterSink") + .def_static("make", &UpxAdapterSink::make); + + /* prints + * std::pair + * pairs + */ + m.def("make_realization_printer", &UpxToConsole::make); + +#ifdef OBSOLETE + /* this implementation fails -- looks like .so libraries + * have separate typeinfo for std::pair + * and don't find each other. + */ + m.def("make_realization_printer2", + [] + { + return reactor::TemporaryTest::realization_printer(); + }); +#endif + + } /*pyprocess*/ + } /*namespace process*/ +} /*namespace xo*/ + +/* end pyprocess.cpp */ diff --git a/src/pyprocess/pyprocess.hpp.in b/src/pyprocess/pyprocess.hpp.in new file mode 100644 index 00000000..d2f5cec1 --- /dev/null +++ b/src/pyprocess/pyprocess.hpp.in @@ -0,0 +1,25 @@ +/* @file pyprocess.hpp + * + * automatically generated from src/pyprocess/pyprocess.hpp.in + * see src/pyprocess/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYPROCESS_MODULE_NAME(), m) { ... } + */ +#define PYPROCESS_MODULE_NAME() @SELF_LIBRARY_NAME@ + +/* example: + * py::module_::import(PYPROCESS_MODULE_NAME_STR) + */ +#define PYPROCESS_MODULE_NAME_STR "@SELF_LIBRARY_NAME@" + +/* example: + * PYPROCESS_IMPORT_MODULE() + * replaces + * py::module_::import("pyprocess") + */ +#define PYPROCESS_IMPORT_MODULE() py::module_::import("@SELF_LIBRARY_NAME@") + +/* end pyprocess.hpp */ From ce5c9145667a842b52c36670ffa1a601b9bc7220 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:10:51 -0400 Subject: [PATCH 0313/2524] + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f52f1311 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# lsp keeps state here +.cache +# typical build directory +build +# lsp: symlink to file in build directory (established manually) +compile_commands.json From 1a1383c724524af5419e9a486b994f7f04e0ec7a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:13:32 -0400 Subject: [PATCH 0314/2524] compile fix: include path to pyprocess.hpp --- src/pyprocess/pyprocess.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyprocess/pyprocess.cpp b/src/pyprocess/pyprocess.cpp index 21c2f287..56b5b1f8 100644 --- a/src/pyprocess/pyprocess.cpp +++ b/src/pyprocess/pyprocess.cpp @@ -1,7 +1,7 @@ /* @file pyprocess.cpp */ // note: need pyprocess/ here bc pyprocess.hpp is generated, located in build directory -#include "src/pyprocess/pyprocess.hpp" +#include "pyprocess.hpp" #include "xo/pywebutil/pywebutil.hpp" #include "xo/process/init_process.hpp" #include "xo/process/UpxToConsole.hpp" From 060366684e54c0dd1a692e2237ffea3c3dbfeb38 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:16:29 -0400 Subject: [PATCH 0315/2524] github: + workflow --- .github/workflows/main.yml | 234 +++++++++++++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..3bde7ce1 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,234 @@ +name: build xo-pyreactor + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + - name: Install pybind11-dev + run: sudo apt-get install -y pybind11-dev + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/reactor + + - name: Configure reactor + # configure cmake for reactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor + + - name: Build reactor + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Install reactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reactor + + # ---------------------------------------------------------------- + + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + + - name: Clone pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/pyutil + + - name: Configure pyutil + # configure cmake for pyutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyutil + + - name: Build pyutil + run: cmake --build ${{github.workspace}}/build_pyutil --config ${{env.BUILD_TYPE}} + + - name: Install pyutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyutil + + # ---------------------------------------------------------------- + + - name: Clone pyreflect + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyreflect + path: repo/pyreflect + + - name: Configure pyreflect + # configure cmake for pyreflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyreflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyreflect + + - name: Build pyreflect + run: cmake --build ${{github.workspace}}/build_pyreflect --config ${{env.BUILD_TYPE}} + + - name: Install pyreflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyreflect + + # ---------------------------------------------------------------- + + - name: Clone pyprintjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyprintjson + path: repo/pyprintjson + + - name: Configure pyprintjson + # configure cmake for pyprintjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyprintjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyprintjson + + - name: Build pyprintjson + run: cmake --build ${{github.workspace}}/build_pyprintjson --config ${{env.BUILD_TYPE}} + + - name: Install pyprintjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyprintjson + + # ---------------------------------------------------------------- + + - name: Configure self (pyreactor) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_pyreactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (pyreactor) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_pyreactor --config ${{env.BUILD_TYPE}} + + - name: Test self (pyreactor) + working-directory: ${{github.workspace}}/build_pyreactor + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 5c4fb100563be70ea675e3b00436c920197720be Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:22:20 -0400 Subject: [PATCH 0316/2524] github: + missing webutil dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3bde7ce1..c05316bf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -123,6 +123,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + - name: Clone reactor uses: actions/checkout@v3 with: From 0653f4f69ed9a8fd2a80850de8292fd0cd9c1be8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:24:36 -0400 Subject: [PATCH 0317/2524] github: + missing callback dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c05316bf..ffcf823b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -123,6 +123,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + - name: Clone webutil uses: actions/checkout@v3 with: From a97de592dfb37033ad828ced22157049b762c6c0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:29:50 -0400 Subject: [PATCH 0318/2524] github: fix dep ordering --- .github/workflows/main.yml | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ffcf823b..7f1a931a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -161,25 +161,6 @@ jobs: # ---------------------------------------------------------------- - - name: Clone reactor - uses: actions/checkout@v3 - with: - repository: Rconybea/xo-reactor - path: repo/reactor - - - name: Configure reactor - # configure cmake for reactor in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor - - - name: Build reactor - run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} - - - name: Install reactor - # install into ${{github.workspace}}/local - run: cmake --install ${{github.workspace}}/build_reactor - - # ---------------------------------------------------------------- - - name: Clone printjson uses: actions/checkout@v3 with: @@ -199,6 +180,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/reactor + + - name: Configure reactor + # configure cmake for reactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor + + - name: Build reactor + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Install reactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reactor + + # ---------------------------------------------------------------- + - name: Clone pyutil uses: actions/checkout@v3 with: From def29aeb9684836066c9d1f5ed8f67d23620e802 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:36:39 -0400 Subject: [PATCH 0319/2524] github: + missing dep randomgen --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7f1a931a..6d8ba190 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -180,6 +180,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen + + # ---------------------------------------------------------------- + - name: Clone reactor uses: actions/checkout@v3 with: From 45d799bd3648a5b507ef5b2598894948aa588837 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 17:42:50 -0400 Subject: [PATCH 0320/2524] github: + missing xo-ordinaltree dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6d8ba190..fb304ec2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -199,6 +199,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + - name: Clone reactor uses: actions/checkout@v3 with: From 20b899fd5ef418ad56f8d5679c4c8ad1ff0cd2f3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 18:05:25 -0400 Subject: [PATCH 0321/2524] github: + workflow --- .github/workflows/main.yml | 386 +++++++++++++++++++++++++++++++++++++ 1 file changed, 386 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..4bfa8333 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,386 @@ +name: build xo-pyprocess + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + - name: Install pybind11-dev + run: sudo apt-get install -y pybind11-dev + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + + - name: Clone pywebutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pywebutil + path: repo/pywebutil + + - name: Configure pywebutil + # configure cmake for pywebutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pywebutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pywebutil + + - name: Build pywebutil + run: cmake --build ${{github.workspace}}/build_pywebutil --config ${{env.BUILD_TYPE}} + + - name: Install pywebutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pywebutil + + # ---------------------------------------------------------------- + + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen + + # ---------------------------------------------------------------- + + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + + - name: Clone reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/reactor + + - name: Configure reactor + # configure cmake for reactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor + + - name: Build reactor + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Install reactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reactor + + # ---------------------------------------------------------------- + + - name: Clone simulator + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-simulator + path: repo/simulator + + - name: Configure simulator + # configure cmake for simulator in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_simulator -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/simulator + + - name: Build simulator + run: cmake --build ${{github.workspace}}/build_simulator --config ${{env.BUILD_TYPE}} + + - name: Install simulator + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_simulator + + # ---------------------------------------------------------------- + + - name: Clone process + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-process + path: repo/process + + - name: Configure process + # configure cmake for process in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_process -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/process + + - name: Build process + run: cmake --build ${{github.workspace}}/build_process --config ${{env.BUILD_TYPE}} + + - name: Install process + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_process + + # ---------------------------------------------------------------- + + - name: Clone pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/pyutil + + - name: Configure pyutil + # configure cmake for pyutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyutil + + - name: Build pyutil + run: cmake --build ${{github.workspace}}/build_pyutil --config ${{env.BUILD_TYPE}} + + - name: Install pyutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyutil + + # ---------------------------------------------------------------- + + - name: Clone pyreflect + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyreflect + path: repo/pyreflect + + - name: Configure pyreflect + # configure cmake for pyreflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyreflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyreflect + + - name: Build pyreflect + run: cmake --build ${{github.workspace}}/build_pyreflect --config ${{env.BUILD_TYPE}} + + - name: Install pyreflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyreflect + + # ---------------------------------------------------------------- + + - name: Clone pyprintjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyprintjson + path: repo/pyprintjson + + - name: Configure pyprintjson + # configure cmake for pyprintjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyprintjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyprintjson + + - name: Build pyprintjson + run: cmake --build ${{github.workspace}}/build_pyprintjson --config ${{env.BUILD_TYPE}} + + - name: Install pyprintjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyprintjson + + # ---------------------------------------------------------------- + + - name: Clone pyreactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyreactor + path: repo/pyreactor + + - name: Configure pyreactor + # configure cmake for pyreactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyreactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyreactor + + - name: Build pyreactor + run: cmake --build ${{github.workspace}}/build_pyreactor --config ${{env.BUILD_TYPE}} + + - name: Install pyreactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyreactor + + # ---------------------------------------------------------------- + + - name: Configure self (pyprocess) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_pyprocess -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (pyprocess) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_pyprocess --config ${{env.BUILD_TYPE}} + + - name: Test self (pyprocess) + working-directory: ${{github.workspace}}/build_pyprocess + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 1fe15afb57f26504d9e4d7e1f7ad6d8fd03b0505 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Oct 2023 18:07:50 -0400 Subject: [PATCH 0322/2524] github: fix dep ordering --- .github/workflows/main.yml | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4bfa8333..a760a879 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -161,6 +161,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/pyutil + + - name: Configure pyutil + # configure cmake for pyutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_pyutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyutil + + - name: Build pyutil + run: cmake --build ${{github.workspace}}/build_pyutil --config ${{env.BUILD_TYPE}} + + - name: Install pyutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_pyutil + + # ---------------------------------------------------------------- + - name: Clone pywebutil uses: actions/checkout@v3 with: @@ -294,25 +313,6 @@ jobs: # ---------------------------------------------------------------- - - name: Clone pyutil - uses: actions/checkout@v3 - with: - repository: Rconybea/xo-pyutil - path: repo/pyutil - - - name: Configure pyutil - # configure cmake for pyutil in dedicated build directory. - run: cmake -B ${{github.workspace}}/build_pyutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/pyutil - - - name: Build pyutil - run: cmake --build ${{github.workspace}}/build_pyutil --config ${{env.BUILD_TYPE}} - - - name: Install pyutil - # install into ${{github.workspace}}/local - run: cmake --install ${{github.workspace}}/build_pyutil - - # ---------------------------------------------------------------- - - name: Clone pyreflect uses: actions/checkout@v3 with: From 0068f6c0fda6b270c9e7481a17a7d0556be15f60 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 00:00:41 -0400 Subject: [PATCH 0323/2524] build: concept carveout for clang + std::copyable --- include/xo/randomgen/distribution_concept.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/xo/randomgen/distribution_concept.hpp b/include/xo/randomgen/distribution_concept.hpp index 61c764e0..979a64d5 100644 --- a/include/xo/randomgen/distribution_concept.hpp +++ b/include/xo/randomgen/distribution_concept.hpp @@ -24,12 +24,16 @@ namespace xo { // os << dist // is >> dist - } && std::copyable + } +#ifdef __clang__ + // std::copyable apparently not available in clang11 ? +#else + && std::copyable && std::copyable +#endif && std::equality_comparable; } /*namespace rng*/ } /*namespace xo*/ - /* end distribution_concept.hpp */ From a6d0252dd46c19772370fab8e752c15497f0ca64 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 12:15:18 -0400 Subject: [PATCH 0324/2524] build: clang16 fixes (c++20 concept headers available) --- include/xo/randomgen/distribution_concept.hpp | 3 ++- include/xo/randomgen/engine_concept.hpp | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/include/xo/randomgen/distribution_concept.hpp b/include/xo/randomgen/distribution_concept.hpp index 979a64d5..c2343255 100644 --- a/include/xo/randomgen/distribution_concept.hpp +++ b/include/xo/randomgen/distribution_concept.hpp @@ -30,8 +30,9 @@ namespace xo { #else && std::copyable && std::copyable + && std::equality_comparable #endif - && std::equality_comparable; + ; } /*namespace rng*/ } /*namespace xo*/ diff --git a/include/xo/randomgen/engine_concept.hpp b/include/xo/randomgen/engine_concept.hpp index 38b65cd7..2b3a2e53 100644 --- a/include/xo/randomgen/engine_concept.hpp +++ b/include/xo/randomgen/engine_concept.hpp @@ -7,6 +7,8 @@ namespace std { #ifdef __clang__ + +# if __clang_major__ <= 11 template < class T > concept integral = std::is_integral_v; @@ -31,8 +33,11 @@ namespace std { && requires { { G::min() } -> std::same_as>; { G::max() } -> std::same_as>; requires std::bool_constant<(G::min() < G::max())>::value; }; +# endif + #else /* uniform_random_bit_generator provided by gcc 12.3.2 */ + /* uniform_random_bit_generator provided by clang 16 */ #endif } /*namespace std*/ From a9c73175e2a8f8e1cd9993dad3d0779c9b67f751 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 12:21:14 -0400 Subject: [PATCH 0325/2524] build: fix deps -- need reactor + printjson --- src/process/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/process/CMakeLists.txt b/src/process/CMakeLists.txt index c6033dde..9be5b87b 100644 --- a/src/process/CMakeLists.txt +++ b/src/process/CMakeLists.txt @@ -14,6 +14,6 @@ xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 $ # must coordinate with find_dependency() calls # in xo-process/cmake/processConfig.cmake.in # -xo_dependency(${SELF_LIB} reflect) -#xo_dependency(${SELF_LIB} webutil) +xo_dependency(${SELF_LIB} reactor) +xo_dependency(${SELF_LIB} printjson) #xo_dependency(${SELF_LIB} callback) From 450b17807ab191611014b04954ed7a90e8d5768e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 15:25:52 -0400 Subject: [PATCH 0326/2524] + XO_SYMLINK_INSTALL + symlink-only install variations --- cmake/xo_cxx.cmake | 95 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 478cb80b..bcb6f7b7 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -187,14 +187,85 @@ macro(xo_compile_options target) target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) endmacro() +# ---------------------------------------------------------------- +# +# establish default value for XO_SYMLINK_INSTALL. +# +# may want to use this for a nested build, +# where we want to run cmake for nested codebase using externalproject_add(). +# +# in this case: +# 1. will need to build+install nested project foo to temporary location in build tree, +# so that build artifacts (such as fooConfig.cmake) are available to depending +# projects +# 2. bona fide install with (for example) copied .hpp files interferes with +# cross-codebase development. +# 2a. want changes to original .hpp files to trigger rebuild of depending codebases. +# This won't happen if depending project relies on a copy +# 2b. want IDE that observes compiler commands (i.e. LSP) to visit .hpp files +# in their original codebase, since that's the correct place to make any edits. +# +# see +# - xo_install_include_tree() +# +macro(xo_establish_symlink_install) + if(NOT DEFINED XO_SYMLINK_INSTALL) + set(XO_SYMLINK_INSTALL False) + endif() +endmacro() + # ---------------------------------------------------------------- # use this to install typical include file subtree # macro(xo_install_include_tree) - install( - DIRECTORY ${PROJECT_SOURCE_DIR}/include/ - FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ - DESTINATION ${CMAKE_INSTALL_PREFIX}/include) + xo_establish_symlink_install() + + if(XO_SYMLINK_INSTALL) + message(FATAL_ERROR "symlink install not implemented for ${PROJECT_SOURCE_DIR}/include -- use xo_install_include_tree3()") + else() + install( + DIRECTORY ${PROJECT_SOURCE_DIR}/include/ + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/include) + endif() +endmacro() + +# create symlink from ${symlink_path} -> ${dest_path}, +# from +# make install +# +macro(xo_install_make_symlink dest_path symlink_dir symlink_name) + install(CODE "message(\"make_directory: ${symlink_dir}\")") + install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${symlink_dir})") + + install(CODE "message(\"symlink: ${symlink_dir}/${symlink_name} -> ${dest_path}/${symlink_name}\")") + install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink ${dest_path}/${symlink_name} ${symlink_dir}/${symlink_name})") +endmacro() + +# e.g. path = include/xo/foo to install xo-foo/include/xo/foo +# +macro(xo_install_include_tree3 subdir_path) + xo_establish_symlink_install() + + if(XO_SYMLINK_INSTALL) + # ugh. cmake doesn't allow input path argument to cmake_path() + # to be a macro variable. + set(_xo_install_include_tree3_subdir_path ${subdir_path}) + set(_xo_install_include_tree3_dirname "") + set(_xo_install_include_tree3_basename "") + cmake_path(GET _xo_install_include_tree3_subdir_path PARENT_PATH _xo_install_include_tree3_dirname) + cmake_path(GET _xo_install_include_tree3_subdir_path FILENAME _xo_install_include_tree3_basename) + + xo_install_make_symlink( + ${PROJECT_SOURCE_DIR}/${_xo_install_include_tree3_dirname} + ${CMAKE_INSTALL_PREFIX}/${_xo_install_include_tree3_dirname} + ${_xo_install_include_tree3_basename}) + else() + install( + DIRECTORY ${PROJECT_SOURCE_DIR}/${subdir_path} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/${subdir_path}) + endif() endmacro() # ---------------------------------------------------------------- @@ -227,6 +298,22 @@ macro(xo_install_library3 target projectTargets) xo_install_include_tree() endmacro() +macro(xo_install_library4 target projectTargets) + install( + TARGETS ${target} + EXPORT ${projectTargets} + 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 + ) + + xo_install_include_tree3(include/xo/${target}) + + #xo_install_include_tree() -- use xo_install_include_tree3() separately +endmacro() + # ---------------------------------------------------------------- # for projectname=foo, require: From 9dfe2477298c0d77f5d13ece5bc82a7f0557dfc7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 15:41:29 -0400 Subject: [PATCH 0327/2524] xo-cmake: + xo_add_shared_library4() (symlink-hpp-enabled) --- cmake/xo_cxx.cmake | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index bcb6f7b7..51268ea3 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -83,9 +83,38 @@ macro(xo_include_headeronly_options2 target) endmacro() # ---------------------------------------------------------------- -# use this for a shared library. +# use this to introduce a shared library. +# - has symlink-enabled .hpp install +# +macro(xo_add_shared_library4 target projectTargets targetversion soversion sources) + add_library(${target} SHARED ${sources}) + foreach(arg IN ITEMS ${ARGN}) + #message("target=${target}; arg=${arg}") + + # to use PUBLIC here would need to split: + # $ + # $ + # but shouldn't need that, since we arrange to install includes via + # xo_include_options2() below + # + target_sources(${target} PRIVATE ${arg}) + endforeach() + set_target_properties( + ${target} + PROPERTIES + VERSION ${targetversion} + SOVERSION ${soversion}) + xo_compile_options(${target}) + xo_include_options2(${target}) + xo_install_library4(${target} ${projectTargets}) +endmacro() + +# ---------------------------------------------------------------- +# OBSOLETE. prefer xo_add_shared_library4() # macro(xo_add_shared_library3 target projectTargets targetversion soversion sources) + message(WARNING "obsolete call to xo_add_shared_library3(); prefer xo_add_shared_library4()") + add_library(${target} SHARED ${sources}) foreach(arg IN ITEMS ${ARGN}) #message("target=${target}; arg=${arg}") @@ -112,6 +141,8 @@ endmacro() # OBSOLETE. prefer xo_add_shared_library3() # macro(xo_add_shared_library target targetversion soversion sources) + message(WARNING "obsolete call to xo_add_shared_library(); prefer xo_add_shared_library4()") + add_library(${target} SHARED ${sources}) foreach(arg IN ITEMS ${ARGN}) #message("target=${target}; arg=${arg}") From 49844da0e674d3f25f50d154d3ae7566cc1ea700 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 15:46:05 -0400 Subject: [PATCH 0328/2524] minor: +warning on deprecated xo_install_include_tree() --- cmake/xo_cxx.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 51268ea3..380ef28b 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -249,6 +249,8 @@ endmacro() # use this to install typical include file subtree # macro(xo_install_include_tree) + message(WARNING "deprecated xo_install_include_tree(); prefer xo_install_include_tree3()") + xo_establish_symlink_install() if(XO_SYMLINK_INSTALL) From 6bb448708be7e616bff3676a7fa2100b84449df4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 15:46:37 -0400 Subject: [PATCH 0329/2524] build: symlink-aware install --- CMakeLists.txt | 3 ++- src/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 655a14a4..bcb080d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- # install .hpp -xo_install_include_tree() +#xo_install_include_tree() +xo_install_include_tree3(include/xo/cxxutil) # end CMakeLists.txt diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9d35b21d..4622ad44 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,5 @@ set(SELF_LIB refcnt) set(SELF_SRCS Refcounted.cpp Displayable.cpp) -xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_dependency(${SELF_LIB} indentlog) From 7ccc752b00d102cf31a0482294e192e82ed9d604 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:02:33 -0400 Subject: [PATCH 0330/2524] build: support symlink-only install --- CMakeLists.txt | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 189db40a..766058b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,25 +17,15 @@ add_code_coverage() # add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) -set(XO_PROJECT_NAME subsys) +#set(XO_PROJECT_NAME subsys) xo_toplevel_compile_options() # ---------------------------------------------------------------- # installing header-only library -add_library(subsys INTERFACE) -xo_include_headeronly_options2(subsys) -xo_install_library2(subsys) - -# ---------------------------------------------------------------- -# provide find_package() support - +xo_add_headeronly_library(subsys) +xo_install_library4(subsys ${PROJECT_NAME}Targets) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install .hpp - -xo_install_include_tree() - # end CMakeLists.txt From 6d5a14b33557e2ed9abf8a51682bf18ceb77288b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:20:33 -0400 Subject: [PATCH 0331/2524] bugfix: XO_PROJECT_NAME -> PROJECT_NAME in .cmake.in --- cmake/subsysConfig.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/subsysConfig.cmake.in b/cmake/subsysConfig.cmake.in index e13a2c54..9c15f36a 100644 --- a/cmake/subsysConfig.cmake.in +++ b/cmake/subsysConfig.cmake.in @@ -1,4 +1,4 @@ @PACKAGE_INIT@ -include("${CMAKE_CURRENT_LIST_DIR}/@XO_PROJECT_NAME@Targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From ea36e50ea07a256dbadeeb243b1654793e69520b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:26:27 -0400 Subject: [PATCH 0332/2524] minor tweaks : doc , build messaging --- CMakeLists.txt | 6 +++--- README.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bd4ca7a..e7c1b6eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,8 @@ cmake_minimum_required(VERSION 3.10) project(reflect VERSION 0.1) enable_language(CXX) -message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") -message(STATUS "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}") +#message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +#message(STATUS "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}") # common XO cmake macros (see proj/xo-cmake) include(xo_macros/xo_cxx) @@ -50,4 +50,4 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- # install .hpp files -xo_install_include_tree() +#xo_install_include_tree() diff --git a/README.md b/README.md index 40032c62..ecbafe0c 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ ### build + install dependencies -- [github/Rconybea/xo-refcnt](https://github.com/Rconybea/xo-refcnt) -- [github/Rconybea/xo-subsys](https://github.com/Rconybea/xo-subsys) +- [github/Rconybea/refcnt](https://github.com/Rconybea/refcnt) +- [github/Rconybea/subsys](https://github.com/Rconybea/subsys) ### build + install ``` From ee6eb49354e5769cd98dbb1308da0cb0e307e133 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:26:49 -0400 Subject: [PATCH 0333/2524] build: make symlink-aware --- src/reflect/CMakeLists.txt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index 27485780..00d0b520 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -1,20 +1,21 @@ # 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) +set(SELF_LIB reflect) +set(SELF_SRCS TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp atomic/AtomicTdx.cpp pointer/PointerTdx.cpp vector/VectorTdx.cpp struct/StructTdx.cpp struct/StructMember.cpp init_reflect.cpp) -xo_add_shared_library(${SELF_LIBRARY_NAME} ${PROJECT_VERSION} 1 ${SELF_SOURCE_FILES}) +#xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # dependencies: indentlog, ... -xo_dependency(${SELF_LIBRARY_NAME} refcnt) -xo_dependency(${SELF_LIBRARY_NAME} indentlog) -xo_dependency(${SELF_LIBRARY_NAME} subsys) +xo_dependency(${SELF_LIB} refcnt) +xo_dependency(${SELF_LIB} indentlog) +xo_dependency(${SELF_LIB} subsys) # ---------------------------------------------------------------- # 3rd party dependency: boost: -#xo_boost_dependency(${SELF_LIBRARY_NAME}) +#xo_boost_dependency(${SELF_LIB}) # end CMakeLists.txt From e3f6cda58fe00283ca880eb37b33ba44a3a73948 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:30:55 -0400 Subject: [PATCH 0334/2524] build: make symlink-aware --- CMakeLists.txt | 5 ----- src/printjson/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index af42e3e7..99aa239f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,9 +44,4 @@ add_subdirectory(utest) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install .hpp files - -xo_install_include_tree() - # end CMakeLists.txt diff --git a/src/printjson/CMakeLists.txt b/src/printjson/CMakeLists.txt index b0a4d532..2acf74cf 100644 --- a/src/printjson/CMakeLists.txt +++ b/src/printjson/CMakeLists.txt @@ -3,7 +3,7 @@ set(SELF_LIB printjson) set(SELF_SRCS PrintJson.cpp init_printjson.cpp) -xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # dependencies: indentlog, ... From 36a6edc51f217ca1f89ce24dd3776f3f6d9877f9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:34:28 -0400 Subject: [PATCH 0335/2524] build: allow symlink-preferred install --- CMakeLists.txt | 9 +++++---- README.md | 7 ++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c5413d7a..3f68effb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,14 +51,15 @@ add_subdirectory(example) # output targets set(SELF_LIB randomgen) -add_library(${SELF_LIB} INTERFACE) -xo_include_headeronly_options2(${SELF_LIB}) +xo_add_headeronly_library(${SELF_LIB}) +#add_library(${SELF_LIB} INTERFACE) +#xo_include_headeronly_options2(${SELF_LIB}) # ---------------------------------------------------------------- # standard install + provide find_package() support -xo_install_library2(${SELF_LIB}) -xo_install_include_tree() +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +#xo_install_include_tree() xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- diff --git a/README.md b/README.md index 73f13f1b..414a8218 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,12 @@ # random number generators -# to build + install locally +## Getting Started +### build + install dependencies + +- see [github/Rconybea/cmake](https://github.com/Rconybea/xo-cmake) -- cmake modules + +### to build + install locally ``` $ cd randomgen $ mkdir build From f48208072a9d186441989603d03a8a609f69716d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:52:08 -0400 Subject: [PATCH 0336/2524] xo-cmake: + xo_install_library5() --- cmake/xo_cxx.cmake | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 380ef28b..68ed519c 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -347,6 +347,22 @@ macro(xo_install_library4 target projectTargets) #xo_install_include_tree() -- use xo_install_include_tree3() separately endmacro() +macro(xo_install_library5 target nxo_target projectTargets) + install( + TARGETS ${target} + EXPORT ${projectTargets} + 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 + ) + + xo_install_include_tree3(include/xo/${nxo_target}) + + #xo_install_include_tree() -- use xo_install_include_tree3() separately +endmacro() + # ---------------------------------------------------------------- # for projectname=foo, require: From 5b3f712e0fb01151c7b36038faee1a94652ef672 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:52:29 -0400 Subject: [PATCH 0337/2524] build: + symlink-centric install --- CMakeLists.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c7e32668..b5894f9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,15 +50,16 @@ xo_toplevel_compile_options() # ---------------------------------------------------------------- # output targets -set(SELF_LIB xo_pyutil) -add_library(${SELF_LIB} INTERFACE) -xo_include_headeronly_options2(${SELF_LIB}) +set(SELF_SHORTNAME pyutil) +set(SELF_LIB xo_${SELF_SHORTNAME}) +xo_add_headeronly_library(${SELF_LIB}) +#xo_include_headeronly_options2(${SELF_LIB}) # ---------------------------------------------------------------- # standard install + provide find_package() support -xo_install_library2(${SELF_LIB}) -xo_install_include_tree() +xo_install_library5(${SELF_LIB} ${SELF_SHORTNAME} ${PROJECT_NAME}Targets) +#xo_install_include_tree() xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- From aa24e1890956a97e8397a56fc6b170cd628d11a0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 16:56:14 -0400 Subject: [PATCH 0338/2524] build: support symlink-centric variation --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a7b8377..727883e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,8 +41,8 @@ xo_add_headeronly_library(${SELF_LIB}) # ---------------------------------------------------------------- # -xo_install_library3(${SELF_LIB} ${PROJECT_NAME}Targets) -xo_install_include_tree() +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +#xo_install_include_tree() # (note: ..Targets from xo_install_library2()) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) From c0c9cf279eb51299997dcee22f0904defd54354f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:03:47 -0400 Subject: [PATCH 0339/2524] github: nit: name for workflow yaml --- .github/workflows/main.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0971d8de..38eee8cf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,4 @@ -# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. -# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml -name: CMake on a single platform +name: build xo-callback + xo dependencies on: push: From dd01874b2ea023202f3084f92c31199e7fa03e2c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:04:09 -0400 Subject: [PATCH 0340/2524] build: support symlink-enabled variation --- CMakeLists.txt | 5 ----- src/callback/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d67984af..52296781 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,9 +45,4 @@ add_subdirectory(src/callback) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install .hpp files - -xo_install_include_tree() - # end CMakeLists.txt diff --git a/src/callback/CMakeLists.txt b/src/callback/CMakeLists.txt index 114a89e2..cfeaf08c 100644 --- a/src/callback/CMakeLists.txt +++ b/src/callback/CMakeLists.txt @@ -4,7 +4,7 @@ set(SELF_LIB callback) #set(SELF_SRCS CallbackSet.cpp) xo_add_headeronly_library(${SELF_LIB}) -xo_install_library3(${SELF_LIB} ${PROJECT_NAME}Targets) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- # external dependencies: From 7c2d01c5f437ed01bdf72681036b33f40551bb6e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:10:28 -0400 Subject: [PATCH 0341/2524] build: support symlink-enabled variation --- CMakeLists.txt | 7 ------- src/webutil/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 04cc53f2..b63e290e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,10 +42,3 @@ add_subdirectory(src/webutil) # provide find_package() support xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) - -# ---------------------------------------------------------------- -# install bespoke targets, if any - -xo_install_include_tree() - -#install(TARGETS example DESTINATION bin/xo-webutil/example) diff --git a/src/webutil/CMakeLists.txt b/src/webutil/CMakeLists.txt index 7d9f5665..54fe2a88 100644 --- a/src/webutil/CMakeLists.txt +++ b/src/webutil/CMakeLists.txt @@ -4,7 +4,7 @@ set(SELF_LIB webutil) set(SELF_SRCS StreamEndpointDescr.cpp HttpEndpointDescr.cpp Alist.cpp) # reminder: can't be header-only library, because depends on non-header-only callback (bc of non-header-only refcnt) -xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # external dependencies From f26fb23255d49544a6c437ad64b7b0257f50fa64 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:18:21 -0400 Subject: [PATCH 0342/2524] build: support symlink-enabled variation --- CMakeLists.txt | 5 ----- src/reactor/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e0be181..b24e139a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,9 +44,4 @@ add_subdirectory(utest) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install project .hpp files - -xo_install_include_tree() - # end CMakeLists.txt diff --git a/src/reactor/CMakeLists.txt b/src/reactor/CMakeLists.txt index 884e466c..e50e7016 100644 --- a/src/reactor/CMakeLists.txt +++ b/src/reactor/CMakeLists.txt @@ -7,7 +7,7 @@ set(SELF_SRCS Reactor.cpp PollingReactor.cpp init_reactor.cpp) -xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # external dependencies From 32a027e610339794e2a0aab1871a8d1a54f1711c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:18:32 -0400 Subject: [PATCH 0343/2524] github: nit: copypasta for name --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aaef0421..30ac4a91 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: build xo-callback + xo dependencies +name: build xo-reactor + xo dependencies on: push: From fe268c12b29800e5b45872a6dc78f6a49eda2318 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:18:48 -0400 Subject: [PATCH 0344/2524] doc: nits in README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8ab8e106..8136c1da 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ build+install these first ### build + install xo-reactor ``` -$ cd reactor +$ cd xo-reactor $ mkdir build $ cd build $ INSTALL_PREFIX=/usr/local # or wherever you prefer @@ -24,6 +24,8 @@ $ make install ``` (also see .github/workflows/main.yml) +## Development + ### build for unit test coverage ``` $ cd xo-reactor @@ -32,8 +34,6 @@ $ cd ccov $ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. ``` -## Development - ### LSP support LSP looks for compile commands in the root of the source tree; @@ -51,6 +51,6 @@ $ ln -s build/compile_commands.json - `-H` include help text ``` -$ cd reactor/build +$ cd xo-reactor/build $ cmake -LAH ``` From 20fbb27afc0c8be1fbd119772883018af7fe8687 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:29:24 -0400 Subject: [PATCH 0345/2524] build: symlink-aware install variation for pyxxx libraries --- cmake/xo_cxx.cmake | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 68ed519c..2c169b77 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -518,10 +518,19 @@ macro(xo_pybind11_library target projectTargets source_files) ${PROJECT_BINARY_DIR}/${target}.hpp) # was ${PROJECT_SOURCE_DIR}/include/xo/${target}/${target}.hpp) - install( - FILES ${PROJECT_BINARY_DIR}/${target}.hpp - PERMISSIONS OWNER_READ GROUP_READ WORLD_READ - DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${target}) + xo_establish_symlink_install() + + if(XO_SYMLINK_INSTALL) + xo_install_make_symlink( + ${PROJECT_BINARY_DIR} + ${CMAKE_INSTALL_PREFIX}/include/xo/${target} + ${target}.hpp) + else() + install( + FILES ${PROJECT_BINARY_DIR}/${target}.hpp + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${target}) + endif() # find_package(Python..) finds python in # /Library/Frameworks/Python.framework/... From 0119cdfe1c2a884455bc54343128196f56f9c903 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:35:05 -0400 Subject: [PATCH 0346/2524] symlink policy for pybind11 libs --- cmake/xo_cxx.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 2c169b77..0b054171 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -559,7 +559,7 @@ macro(xo_pybind11_library target projectTargets source_files) xo_pybind11_link_flags() xo_include_options2(${target}) - xo_install_library3(${target} ${projectTargets}) + xo_install_library4(${target} ${projectTargets}) endmacro() # ---------------------------------------------------------------- From 71416fcd259b60bd72f0f338859484461c1100e8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 19 Oct 2023 17:42:09 -0400 Subject: [PATCH 0347/2524] xo-cmake: provide canoncial include dir for generated pyfoo.hpp --- cmake/xo_cxx.cmake | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 0b054171..6932c3e8 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -513,21 +513,23 @@ endmacro() # 2. pyfoo/pyfoo.hpp.in -> pyfoo/pyfoo.hpp # macro(xo_pybind11_library target projectTargets source_files) + file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/include/xo/${target}) + configure_file( ${target}.hpp.in - ${PROJECT_BINARY_DIR}/${target}.hpp) + ${PROJECT_BINARY_DIR}/include/xo/${target}/${target}.hpp) # was ${PROJECT_SOURCE_DIR}/include/xo/${target}/${target}.hpp) xo_establish_symlink_install() if(XO_SYMLINK_INSTALL) xo_install_make_symlink( - ${PROJECT_BINARY_DIR} + ${PROJECT_BINARY_DIR}/include/xo/${target} ${CMAKE_INSTALL_PREFIX}/include/xo/${target} ${target}.hpp) else() install( - FILES ${PROJECT_BINARY_DIR}/${target}.hpp + FILES ${PROJECT_BINARY_DIR}/include/xo/${target}/${target}.hpp PERMISSIONS OWNER_READ GROUP_READ WORLD_READ DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${target}) endif() From 2dbda84ef1ff5004418bac8c379e58cb0d6b4a38 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 20 Oct 2023 12:25:10 -0400 Subject: [PATCH 0348/2524] fix include symlink handling for pybind11 libraries --- cmake/xo_cxx.cmake | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index 6932c3e8..eb9fad7f 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -305,6 +305,8 @@ endmacro() # use this for a subdir that builds a library # and supports find_package() # +# note: used deliberately in xo_pybind11_library() below +# macro(xo_install_library2 target) install( TARGETS ${target} @@ -347,6 +349,18 @@ macro(xo_install_library4 target projectTargets) #xo_install_include_tree() -- use xo_install_include_tree3() separately endmacro() +macro(xo_install_library4_noincludes target projectTargets) + install( + TARGETS ${target} + EXPORT ${projectTargets} + 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() + macro(xo_install_library5 target nxo_target projectTargets) install( TARGETS ${target} @@ -524,9 +538,9 @@ macro(xo_pybind11_library target projectTargets source_files) if(XO_SYMLINK_INSTALL) xo_install_make_symlink( - ${PROJECT_BINARY_DIR}/include/xo/${target} - ${CMAKE_INSTALL_PREFIX}/include/xo/${target} - ${target}.hpp) + ${PROJECT_BINARY_DIR}/include/xo + ${CMAKE_INSTALL_PREFIX}/include/xo + ${target}) else() install( FILES ${PROJECT_BINARY_DIR}/include/xo/${target}/${target}.hpp @@ -561,7 +575,9 @@ macro(xo_pybind11_library target projectTargets source_files) xo_pybind11_link_flags() xo_include_options2(${target}) - xo_install_library4(${target} ${projectTargets}) + # don't want to symlink include tree, because lives in build dir. + # see install for generated .hpp above + xo_install_library4_noincludes(${target} ${projectTargets}) endmacro() # ---------------------------------------------------------------- From 729975b14559f8a70ac7b9cf96aa68d4d8f0a29c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 20 Oct 2023 12:32:19 -0400 Subject: [PATCH 0349/2524] tidy: drop dead comments --- CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ccf4b129..b2e5ca94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,8 +43,3 @@ add_subdirectory(src/pyreflect) # provide find_package() support xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) - -# ---------------------------------------------------------------- -# install .hpp files - -#xo_install_include_tree() From 3194fb35f78c94cc825d90cc5f2c5122d9c49e2d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 20 Oct 2023 12:34:06 -0400 Subject: [PATCH 0350/2524] fix include path for generated .hpp --- src/pyreflect/pyreflect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index fc6e8fe8..c9b7f47a 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -1,7 +1,7 @@ /* @file pyreflect.cpp */ // note: need pyreflect/ here bc pyreflect.hpp is generated, located in build directory -#include "pyreflect.hpp" +#include "xo/pyreflect/pyreflect.hpp" #include "xo/reflect/TypeDescr.hpp" #include "xo/reflect/TaggedRcptr.hpp" #include "xo/reflect/SelfTagging.hpp" From a480e40afe636fa94ce4150f555b0c2014bf9faa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 20 Oct 2023 12:39:29 -0400 Subject: [PATCH 0351/2524] build: adopt builddir/include/xo/target in include path --- cmake/xo_cxx.cmake | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cmake/xo_cxx.cmake b/cmake/xo_cxx.cmake index eb9fad7f..54072862 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_cxx.cmake @@ -65,10 +65,11 @@ macro(xo_include_headeronly_options2 target) ${target} INTERFACE $ $ - $ # e.g. for #include "indentlog/scope.hpp" - $ # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED] + $ # e.g. for #include "indentlog/scope.hpp" + #$ # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED] $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect - $ # e.g. for generated .hpp files + #$ # e.g. for generated .hpp files + $ # e.g. for generated .hpp files ) # ---------------------------------------------------------------- @@ -192,10 +193,11 @@ macro(xo_include_options2 target) ${target} PUBLIC $ $ - $ # e.g. for #include "indentlog/scope.hpp" - $ # e.g. for #include "Refcounted.hpp" in refcnt/src [DEPRECATED] + $ # e.g. for #include "indentlog/scope.hpp" + #$ # e.g. for #include "Refcounted.hpp" in refcnt/src [DEPRECATED] $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect - $ # e.g. for generated .hpp files + #$ # e.g. for generated .hpp files + $ # e.g. for generated .hpp files ) # ---------------------------------------------------------------- From 35820ef0dadbe75f70617b7c0ee5108f499a1ea7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:29:03 -0400 Subject: [PATCH 0352/2524] xo-cmake: sub-module build w/out relying on cmake external-project --- CMakeLists.txt | 6 +- cmake/{ => xo_macros}/code-coverage.cmake | 0 cmake/{ => xo_macros}/xo_cxx.cmake | 322 +++++++++++++++++++--- 3 files changed, 293 insertions(+), 35 deletions(-) rename cmake/{ => xo_macros}/code-coverage.cmake (100%) rename cmake/{ => xo_macros}/xo_cxx.cmake (64%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c9aa85f..197416e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,8 +12,8 @@ set(XO_PROJECT_NAME xo_macros) install( FILES - "cmake/xo_cxx.cmake" - "cmake/code-coverage.cmake" + "cmake/xo_macros/xo_cxx.cmake" + "cmake/xo_macros/code-coverage.cmake" PERMISSIONS OWNER_READ GROUP_READ WORLD_READ - DESTINATION share/cmake/${XO_PROJECT_NAME} + DESTINATION share/cmake/xo_macros ) diff --git a/cmake/code-coverage.cmake b/cmake/xo_macros/code-coverage.cmake similarity index 100% rename from cmake/code-coverage.cmake rename to cmake/xo_macros/code-coverage.cmake diff --git a/cmake/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake similarity index 64% rename from cmake/xo_cxx.cmake rename to cmake/xo_macros/xo_cxx.cmake index 54072862..2ab628be 100644 --- a/cmake/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -1,5 +1,21 @@ macro(xo_toplevel_compile_options) + define_property( + TARGET + PROPERTY xo_deps + BRIEF_DOCS "transitive completion of xo-dependencies for a target. Used with XO_SUBMODULE_BUILD" + ) + define_property( + TARGET + PROPERTY xo_srcdir + BRIEF_DOCS "snapshot of PROJECT_SOURCE_DIR asof when this target introduced" + ) + define_property( + TARGET + PROPERTY xo_bindir + BRIEF_DOCS "snapshot of PROJECT_BINARY_DIR asof when this target introduced" + ) + if(NOT DEFINED CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 20) endif() @@ -47,7 +63,11 @@ macro(xo_toplevel_compile_options) endif() endmacro() -macro(xo_include_headeronly_options2 target) +# e.g. +# - xo_target = xo_pyutil +# - nxo_target = pyutil +# +macro(xo_include_headeronly_options5 target nxo_target) # ---------------------------------------------------------------- # PROJECT_SOURCE_DIR: # so we can for example write @@ -64,12 +84,12 @@ macro(xo_include_headeronly_options2 target) target_include_directories( ${target} INTERFACE $ - $ + $ $ # e.g. for #include "indentlog/scope.hpp" #$ # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED] - $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect + $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect #$ # e.g. for generated .hpp files - $ # e.g. for generated .hpp files + $ # e.g. for generated .hpp files ) # ---------------------------------------------------------------- @@ -83,12 +103,26 @@ macro(xo_include_headeronly_options2 target) endif() endmacro() +macro(xo_include_headeronly_options2 target) + xo_include_headeronly_options5(${target} ${target}) +endmacro() + # ---------------------------------------------------------------- # use this to introduce a shared library. # - has symlink-enabled .hpp install # macro(xo_add_shared_library4 target projectTargets targetversion soversion sources) add_library(${target} SHARED ${sources}) + set_property( + TARGET ${target} + PROPERTY xo_deps "${target}") + set_property( + TARGET ${target} + PROPERTY xo_srcdir ${PROJECT_SOURCE_DIR}) + set_property( + TARGET ${target} + PROPERTY xo_bindir ${PROJECT_BINARY_DIR}) + foreach(arg IN ITEMS ${ARGN}) #message("target=${target}; arg=${arg}") @@ -114,7 +148,7 @@ endmacro() # OBSOLETE. prefer xo_add_shared_library4() # macro(xo_add_shared_library3 target projectTargets targetversion soversion sources) - message(WARNING "obsolete call to xo_add_shared_library3(); prefer xo_add_shared_library4()") + message(WARNING "${target}: obsolete call to xo_add_shared_library3(); prefer xo_add_shared_library4()") add_library(${target} SHARED ${sources}) foreach(arg IN ITEMS ${ARGN}) @@ -142,7 +176,7 @@ endmacro() # OBSOLETE. prefer xo_add_shared_library3() # macro(xo_add_shared_library target targetversion soversion sources) - message(WARNING "obsolete call to xo_add_shared_library(); prefer xo_add_shared_library4()") + message(WARNING "${target}: obsolete call to xo_add_shared_library(); prefer xo_add_shared_library4()") add_library(${target} SHARED ${sources}) foreach(arg IN ITEMS ${ARGN}) @@ -169,9 +203,41 @@ endmacro() # ---------------------------------------------------------------- # use this for a header-only library # -macro(xo_add_headeronly_library target) +# e.g. +# - target=xo_pyutil cmake target name for this library +# - nxo_target=pyutil directory for this target under include/xo +# +macro(xo_add_headeronly_library5 target nxo_target) add_library(${target} INTERFACE) - xo_include_headeronly_options2(${target}) + + set_property( + TARGET ${target} + PROPERTY xo_deps "${target}") + set_property( + TARGET ${target} + PROPERTY xo_srcdir ${PROJECT_SOURCE_DIR}) + set_property( + TARGET ${target} + PROPERTY xo_bindir ${PROJECT_BINARY_DIR}) + + xo_include_headeronly_options5(${target} ${nxo_target}) +endmacro() + +macro(xo_add_headeronly_library target) + xo_add_headeronly_library5(${target} ${target}) +endmacro() + +# ---------------------------------------------------------------- +# +# in submodule build each xo codebases cmake files are incorporated +# directly (via add_subdirectory()) into a single umbrella build. +# +# In such case we don't use find_package for xo dependencies +# +macro(xo_establish_submodule_build) + if(NOT DEFINED XO_SUBMODULE_BUILD) + set(XO_SUBMODULE_BUILD False) + endif() endmacro() # ---------------------------------------------------------------- @@ -179,6 +245,10 @@ endmacro() # do not use for header-only subsystems; see xo_include_headeronly_options2() # macro(xo_include_options2 target) + xo_establish_submodule_build() + + #message("xo_include_options2: XO_SUBMODULE_BUILD=${XO_SUBMODULE_BUILD}") + # ---------------------------------------------------------------- # PROJECT_SOURCE_DIR: # so we can for example write @@ -414,6 +484,134 @@ macro(xo_export_cmake_config projectname projectversion projecttargets) ) endmacro() +# ---------------------------------------------------------------- +# helper macro for xo_dependency_helper() see below +# +macro(xo_dependency_helper1 target visibility dep_include_subdir) + target_include_directories(${target} ${visibility} + $) + target_include_directories(${target} ${visibility} + $) + + # note: header directories owned by dep get added to target. + # however, header directories _used_ by dep don't get automatically added to target, + # (for example if referred to from a dep-owned .hpp file) + # +endmacro() + +# ---------------------------------------------------------------- +# dependency helper when depending on an xo codebase +# +# Two flavors: +# - depended-on codebase separately built+installed. +# In this case behaves like any other best-practice cmake dep: +# use find_package() to rely on install .cmake config files +# in PREFIX/lib/cmake +# - depended-on codebase in same umbrella source tree as codebase +# invoking this helper. +# 1. XO_UMBRELLA_SOURCE_DIR gives top of umbrella source tree +# 2. depended-on codebase foo in one of +# XO_UMBRELLA_SOURCE_DIR/repo/{foo, xo-foo} +# +# target: cmake target for which to supply a dependency +# visibility: INTERFACE|PUBLIC +# - INTERFACE must be used when supplying a dependency to a header-only target +# dep: cmake target on which to depend (e.g. xo_pyutil) +# nxo_dep: cmake target without any xo_ prefix. (e.g. pyutil) +# +macro(xo_dependency_helper target visibility dep) + string(REGEX REPLACE "^xo_" "" _nxo_dep ${dep}) + string(REGEX REPLACE "^xo-" "" _nxo_dep ${_nxo_dep}) + + xo_establish_submodule_build() + + if(XO_SUBMODULE_BUILD) + if(EXISTS ${XO_UMBRELLA_SOURCE_DIR}/repo/xo-${_nxo_dep}) + xo_dependency_helper1(${target} ${visibility} repo/xo-${_nxo_dep}/include) + endif() + + if(EXISTS ${XO_UMBRELLA_SOURCE_DIR}/repo/${_nxo_dep}) + xo_dependency_helper1(${target} ${visibility} repo/${_nxo_dep}/include) + endif() + else() + find_package(${dep} CONFIG REQUIRED) + endif() + + if (XO_SUBMODULE_BUILD) + get_target_property(_tmp ${target} LINK_LIBRARIES) + message("xo_dependency_helper: ${target} -> ${dep}: ${target}.LINK_LIBRARIES=${_tmp}") + get_target_property(_tmp ${dep} LINK_LIBRARIES) + message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.LINK_LIBRARIES=${_tmp}") + + get_target_property(_tmp ${target} INTERFACE_LINK_LIBRARIES) + message("xo_dependency_helper: ${target} -> ${dep}: ${target}.INTERFACE_LINK_LIBRARIES=${_tmp}") + get_target_property(_tmp ${dep} INTERFACE_LINK_LIBRARIES) + message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.INTERFACE_LINK_LIBRARIES=${_tmp}") + + # add INCLUDE_DIRECTORIES from ${dep} to ${target}. + # + # 1. we only need this for a submodule build + # 1a. cmake automatically adds ${dep}'s directly-set include directories + # (i.e. attached via target_include_directories(${dep} ..) etc. + # 1b. furthermore, cmake adds transitive deps when we use generated + # cmake support + # 1c. cmake doesn't seem automatically to arrange transitive include directories + # when we have a chain of target_link_libraries() calls. + # (NOTE: it does incorporate include paths from direct dependencies) + # 2. because of 1a, need to exclude ${dep}'s own dirs here (to avoid too-long include path) + # + get_target_property(_depsrcdir ${dep} xo_srcdir) + get_target_property(_depbindir ${dep} xo_bindir) + get_target_property(_tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) + if(_tmp) + message("xo_dependency_helper: ${target}: + ${dep}.INCLUDE_DIRECTORIES: ${_tmp}") + foreach(dir ${_tmp}) + # want to add these to compile line for $target}. + # this will happen automatically for ${dep}'s own directories; + # those will have been added by + # xo_include_options2() / xo_include_headeronly_options2() + # however we also need directories for ${dep}'s transitive dependencies + # + if(${dir} MATCHES "BUILD_INTERFACE") + message("xo_dependency_helper: ${target} -> ${dep}: consider dir=${dir}") + if(${dir} MATCHES ${_depsrcdir}) + #message(" skip ${dir}") + elseif(${dir} MATCHES ${_depbindir}) + #message(" skip ${dir}") + else() + message(" KEEP ${dir}") + target_include_directories(${target} ${visibility} ${dir}) + endif() + endif() + +# set_property( +# TARGET ${target} +# APPEND +# PROPERTY INCLUDE_DIRECTORIES ${dir}) + endforeach() + get_target_property(_tmp ${target} INTERFACE_INCLUDE_DIRECTORIES) + list(REMOVE_DUPLICATES _tmp) + set_property( + TARGET ${target} + PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${_tmp}) + #message("xo_dependency_helper: ${target}.INCLUDE_DIRECTORIES: ${_tmp}") + endif() + endif() + + # want ${target}.xo_deps to contain union of previous value, and ${dep}.xo_deps + set_property( + TARGET ${target} + APPEND + PROPERTY xo_deps ${dep}) + get_target_property(_tmp ${target} xo_deps) + list(REMOVE_DUPLICATES _tmp) + set_property( + TARGET ${target} + PROPERTY xo_deps ${_tmp}) + + #list(REMOVE_DUPLICATES _xo_dependency_tmp1) +endmacro() + # ---------------------------------------------------------------- # # dependency on an xo library (including header-only libraries) @@ -429,37 +627,64 @@ endmacro() # dep: name of required dependency, e.g. indentlog # macro(xo_dependency target dep) - find_package(${dep} CONFIG REQUIRED) + xo_establish_submodule_build() + + #message("xo_dependency: XO_SUBMODULE_BUILD=${XO_SUBMODULE_BUILD}") + #message("xo_dependency: XO_UMBRELLA_SOURCE_DIR=${XO_UMBRELLA_SOURCE_DIR}") + #message("xo_dependency: PROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}") + + xo_dependency_helper(${target} PUBLIC ${dep}) + + message("----------------------------------------------------------------") + #message("xo_dependency: ${target}.xo_deps.pre=${_xo_dependency_tmp0}") + #message("xo_dependency: ${dep}.xo_deps=${_xo_dependency_tmp2}") + get_target_property(_tmp ${target} xo_deps) + message("xo_dependency: ${target}.xo_deps=${_tmp}") + #get_target_property(_tmp ${target} INCLUDE_DIRECTORIES) + #message("xo_dependency: ${target}.INCLUDE_DIRECTORIES=${_tmp} before target_link_libraries with ${dep}") + target_link_libraries(${target} PUBLIC ${dep}) + #target_link_libraries(${target} ${dep}) + + #get_target_property(_tmp ${target} INCLUDE_DIRECTORIES) + #message("xo_dependency: ${target}.INCLUDE_DIRECTORIES=${_tmp} after target_link_libraries with ${dep}") + #get_target_property(_tmp ${dep} INCLUDE_DIRECTORIES) + #message("xo_dependency: ${dep}.INCLUDE_DIRECTORIES=${_tmp}") + #get_target_property(_tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) + #message("xo_dependency: ${dep}.INTERFACE_INCLUDE_DIRECTORIES=${_tmp}") + message("----------------------------------------------------------------") endmacro() # dependency of a header-only library on another header-only library # macro(xo_headeronly_dependency target dep) - find_package(${dep} CONFIG REQUIRED) - # Conflict here between PUBLIC and INTERFACE - # - # PUBLIC ensures that include directories required by ${dep} will also be included in compilation of ${target}; - # i.e. will appear in property ${target}.INCLUDE_DIRECTORIES - # - # INTERFACE mandatory when depending on a header-only library (created with add_library(foo INTERFACE)). - # otherwise get error: - # INTERFACE library can only be used with the INTERFACE keyword of - # target_link_libraries - # Unfortunately target_link_libraries() does not copy dependent's INTERFACE_INCLUDE_DIRECTORIES property - # (at least asof cmake 3.25.3). Dependent's INCLUDE_DIRECTORIES property will be empty, since it's header-only. - # - # Workaround by copying property explicity, which we do below - # - target_link_libraries(${target} INTERFACE ${dep}) + xo_establish_submodule_build() -# get_target_property(xo_dependency_headeronly__tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) -# set_property( -# TARGET ${target} -# APPEND PROPERTY INCLUDE_DIRECTORIES ${xo_dependency_headeronly__tmp}) + xo_dependency_helper(${target} INTERFACE ${dep}) + + # Conflict here between PUBLIC and INTERFACE + # + # PUBLIC ensures that include directories required by ${dep} will also be included in compilation of ${target}; + # i.e. will appear in property ${target}.INCLUDE_DIRECTORIES + # + # INTERFACE mandatory when depending on a header-only library (created with add_library(foo INTERFACE)). + # otherwise get error: + # INTERFACE library can only be used with the INTERFACE keyword of + # target_link_libraries + # Unfortunately target_link_libraries() does not copy dependent's INTERFACE_INCLUDE_DIRECTORIES property + # (at least asof cmake 3.25.3). Dependent's INCLUDE_DIRECTORIES property will be empty, since it's header-only. + # + # Workaround by copying property explicity, which we do below + # + target_link_libraries(${target} INTERFACE ${dep}) + + # get_target_property(xo_dependency_headeronly__tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) + # set_property( + # TARGET ${target} + # APPEND PROPERTY INCLUDE_DIRECTORIES ${xo_dependency_headeronly__tmp}) endmacro() -# dependency on namespaced target +# dependency on external (non-xo) namespaced target # e.g. # add_library(foo ..) or add_executable(foo ...) # then @@ -471,6 +696,13 @@ endmacro() macro(xo_external_target_dependency target pkg pkgtarget) find_package(${pkg} CONFIG REQUIRED) target_link_libraries(${target} PUBLIC ${pkgtarget}) + #target_link_libraries(${target} ${pkgtarget}) +endmacro() + +# dependency on external (non-xo) target +# +macro(xo_external_dependency target pkg) + xo_external_target_dependency(${target} ${pkg} ${target}) endmacro() # dependency on target provided from this codebase. @@ -575,6 +807,16 @@ macro(xo_pybind11_library target projectTargets source_files) # pybind11_add_module(${target} MODULE ${source_files}) + set_property( + TARGET ${target} + PROPERTY xo_deps "${target}") + set_property( + TARGET ${target} + PROPERTY xo_srcdir ${PROJECT_SOURCE_DIR}) + set_property( + TARGET ${target} + PROPERTY xo_bindir ${PROJECT_BINARY_DIR}) + xo_pybind11_link_flags() xo_include_options2(${target}) # don't want to symlink include tree, because lives in build dir. @@ -613,10 +855,26 @@ endmacro() # -- conceivably true if libfoo.so has RUNPATH etc. # macro(xo_pybind11_dependency target dep) - find_package(${dep} CONFIG REQUIRED) + xo_establish_submodule_build() + + xo_dependency_helper(${target} PUBLIC ${dep}) # clobber secondary dependencies, as discussed above - set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") + if(XO_SUBMODULE_BUILD) + # ok to keep dep libraries on link line in submodule build + message("xo_pybind11_dependency: ${target}: don't clobber ${dep}.INTERFACE_LINK_LIBRARIES") + else() + message("xo_pybind11_dependency: ${target}: clobbering ${dep}.INTERFACE_LINK_LIBRARIES") + set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") + endif() # now that secondary deps are gone, attach to target pybind11 library # skip xo_dependency() here, that would repeat the find_package() expansion target_link_libraries(${target} PUBLIC ${dep}) endmacro() + +# use when one xo pybind library imports another. +# for example +# pyprintjson -> pyreflect +# +macro(xo_pybind11_header_dependency target dep) + xo_dependency_helper(${target} PUBLIC ${dep}) +endmacro() From fa5e4216a8c5f1bb7ea14a91ef1b10d8cfa085fe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:49:21 -0400 Subject: [PATCH 0353/2524] globally-unique cmake target names for submodule build --- CMakeLists.txt | 2 +- example/ex1/CMakeLists.txt | 4 ++-- example/ex2/CMakeLists.txt | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f68effb..8506e5b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,4 +65,4 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- # install additional components -install(TARGETS ex1 DESTINATION bin/randomgen/example) +install(TARGETS randomgen_ex1 DESTINATION bin/randomgen/example) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt index a9d1b27d..0c809818 100644 --- a/example/ex1/CMakeLists.txt +++ b/example/ex1/CMakeLists.txt @@ -1,2 +1,2 @@ -add_executable(ex1 ex1.cpp) -xo_include_options2(ex1) +add_executable(randomgen_ex1 ex1.cpp) +xo_include_options2(randomgen_ex1) diff --git a/example/ex2/CMakeLists.txt b/example/ex2/CMakeLists.txt index 78016d45..877fae36 100644 --- a/example/ex2/CMakeLists.txt +++ b/example/ex2/CMakeLists.txt @@ -1,3 +1,3 @@ -add_executable(ex2 ex2.cpp) -xo_include_options2(ex2) -xo_dependency(ex2 indentlog) +add_executable(randomgen_ex2 ex2.cpp) +xo_include_options2(randomgen_ex2) +xo_dependency(randomgen_ex2 indentlog) From 7b433e3f4ec0a162ac8bca7058b9b68156f3c9fc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:52:39 -0400 Subject: [PATCH 0354/2524] build: .cmake code layout --- src/reflect/CMakeLists.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index 00d0b520..397876fa 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -1,9 +1,14 @@ # reflect/CMakeLists.txt set(SELF_LIB reflect) -set(SELF_SRCS TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp atomic/AtomicTdx.cpp pointer/PointerTdx.cpp vector/VectorTdx.cpp struct/StructTdx.cpp struct/StructMember.cpp init_reflect.cpp) +set(SELF_SRCS + TypeDescr.cpp TypeDescrExtra.cpp TaggedRcptr.cpp + atomic/AtomicTdx.cpp + pointer/PointerTdx.cpp + vector/VectorTdx.cpp + struct/StructTdx.cpp struct/StructMember.cpp + init_reflect.cpp) -#xo_add_shared_library(${SELF_LIB} ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- From e3e50aca2c70d724cf16f3ecd1a5d4bd10b2047e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:54:07 -0400 Subject: [PATCH 0355/2524] build: streamline include-tree install --- CMakeLists.txt | 5 ----- src/process/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b21be0ba..8d562914 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,9 +44,4 @@ add_subdirectory(utest) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install project .hpp files - -xo_install_include_tree() - # end CMakeLists.txt diff --git a/src/process/CMakeLists.txt b/src/process/CMakeLists.txt index 9be5b87b..c7bb3abe 100644 --- a/src/process/CMakeLists.txt +++ b/src/process/CMakeLists.txt @@ -5,7 +5,7 @@ set(SELF_SRCS BrownianMotion.cpp ExpProcess.cpp Realization.cpp UpxEvent.cpp UpxToConsole.cpp init_process.cpp) -xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # external dependencies From 66a668703951dda9abcd6ecbba45e8b47ecbef22 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:54:40 -0400 Subject: [PATCH 0356/2524] pyprintjson: + pyreflect dep --- src/pyprintjson/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyprintjson/CMakeLists.txt b/src/pyprintjson/CMakeLists.txt index 5e77c995..d73baad1 100644 --- a/src/pyprintjson/CMakeLists.txt +++ b/src/pyprintjson/CMakeLists.txt @@ -6,3 +6,4 @@ set(SELF_SRCS pyprintjson.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} printjson) +xo_pybind11_header_dependency(${SELF_LIB} pyreflect) From 5553fe5f706eb87bf2af158002720e355dbde910 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:55:03 -0400 Subject: [PATCH 0357/2524] pyreactor: + pyprintjson dep --- src/pyreactor/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyreactor/CMakeLists.txt b/src/pyreactor/CMakeLists.txt index ad752e96..b7d5695f 100644 --- a/src/pyreactor/CMakeLists.txt +++ b/src/pyreactor/CMakeLists.txt @@ -6,3 +6,4 @@ set(SELF_SRCS pyreactor.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} reactor) +xo_pybind11_header_dependency(${SELF_LIB} pyprintjson) From 74a23f1132b54155567a9629b19b17d4a9feb2d6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:55:32 -0400 Subject: [PATCH 0358/2524] pyreflect: + xo-pyutil dep --- src/pyreflect/CMakeLists.txt | 2 +- src/pyreflect/pyreflect.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyreflect/CMakeLists.txt b/src/pyreflect/CMakeLists.txt index 94a71814..64be5e08 100644 --- a/src/pyreflect/CMakeLists.txt +++ b/src/pyreflect/CMakeLists.txt @@ -9,4 +9,4 @@ set(SELF_SRCS pyreflect.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} reflect) - +xo_pybind11_dependency(${SELF_LIB} xo_pyutil) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index c9b7f47a..fc6e8fe8 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -1,7 +1,7 @@ /* @file pyreflect.cpp */ // note: need pyreflect/ here bc pyreflect.hpp is generated, located in build directory -#include "xo/pyreflect/pyreflect.hpp" +#include "pyreflect.hpp" #include "xo/reflect/TypeDescr.hpp" #include "xo/reflect/TaggedRcptr.hpp" #include "xo/reflect/SelfTagging.hpp" From e548a3e8ed063767d36d095a527620082421e94b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:56:52 -0400 Subject: [PATCH 0359/2524] build: handle cmake target called xo_pyutil instead of pyutil --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b5894f9a..f85dd952 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ xo_toplevel_compile_options() set(SELF_SHORTNAME pyutil) set(SELF_LIB xo_${SELF_SHORTNAME}) -xo_add_headeronly_library(${SELF_LIB}) +xo_add_headeronly_library5(${SELF_LIB} ${SELF_SHORTNAME}) #xo_include_headeronly_options2(${SELF_LIB}) # ---------------------------------------------------------------- From 8036f787e16541a03b7bc48a70b9524ecba04527 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:57:48 -0400 Subject: [PATCH 0360/2524] build: + pyutil dep --- src/pywebutil/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pywebutil/CMakeLists.txt b/src/pywebutil/CMakeLists.txt index 0a8dc225..95f8df26 100644 --- a/src/pywebutil/CMakeLists.txt +++ b/src/pywebutil/CMakeLists.txt @@ -5,3 +5,4 @@ set(SELF_SRCS pywebutil.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} webutil) +xo_pybind11_dependency(${SELF_LIB} xo_pyutil) From a115153dd0a2bd20be42548ef53c8ff6870391d2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:58:12 -0400 Subject: [PATCH 0361/2524] build: + missing deps (noticed in submodule build) --- src/reactor/CMakeLists.txt | 1 + utest/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/src/reactor/CMakeLists.txt b/src/reactor/CMakeLists.txt index e50e7016..f65772c1 100644 --- a/src/reactor/CMakeLists.txt +++ b/src/reactor/CMakeLists.txt @@ -21,3 +21,4 @@ xo_dependency(${SELF_LIB} reflect) xo_dependency(${SELF_LIB} webutil) xo_dependency(${SELF_LIB} printjson) xo_dependency(${SELF_LIB} callback) +xo_dependency(${SELF_LIB} ordinaltree) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 1ab4b440..1f538cd2 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -13,6 +13,7 @@ target_code_coverage(${SELF_EXE} AUTO ALL) # internal dependency (on this codebase) xo_self_dependency(${SELF_EXE} reactor) +xo_dependency(${SELF_EXE} randomgen) # ---------------------------------------------------------------- # external dependencies From c09dfcc4f526ba1cdc45bccef4761ec903ba13b8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:58:37 -0400 Subject: [PATCH 0362/2524] build: streamling include-tree install --- CMakeLists.txt | 5 ----- src/simulator/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f99f0a2..82d94e6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,9 +44,4 @@ add_subdirectory(src/simulator) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install project .hpp files - -xo_install_include_tree() - # end CMakeLists.txt diff --git a/src/simulator/CMakeLists.txt b/src/simulator/CMakeLists.txt index 172ee4c4..3a7879f5 100644 --- a/src/simulator/CMakeLists.txt +++ b/src/simulator/CMakeLists.txt @@ -5,7 +5,7 @@ set(SELF_SRCS Simulator.cpp SourceTimestamp.cpp init_simulator.cpp) -xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # external dependencies From fa0444daa1534dd10ff30b7224768e3b094db868 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 14:59:00 -0400 Subject: [PATCH 0363/2524] build: streamline include-tree install --- CMakeLists.txt | 5 ----- src/websock/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 51006eed..2178a12d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,9 +44,4 @@ add_subdirectory(src/websock) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) -# ---------------------------------------------------------------- -# install .hpp files - -xo_install_include_tree() - # end CMakeLists.txt diff --git a/src/websock/CMakeLists.txt b/src/websock/CMakeLists.txt index a1f15e18..42f37a7d 100644 --- a/src/websock/CMakeLists.txt +++ b/src/websock/CMakeLists.txt @@ -3,7 +3,7 @@ set(SELF_LIB websock) set(SELF_SRCS EndpointUtil.cpp DynamicEndpoint.cpp WebsockUtil.cpp WebsocketSink.cpp Webserver.cpp) -xo_add_shared_library3(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- # external dependencies From 7add6297923d4e92a0d55ef14d29ec36c6498e2e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 15:13:26 -0400 Subject: [PATCH 0364/2524] build: cmake target must match cmake config templates --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 727883e5..d148b38e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,7 @@ add_subdirectory(utest) # ---------------------------------------------------------------- # header-only library -set(SELF_LIB ordinaltree) +set(SELF_LIB xo_ordinaltree) xo_add_headeronly_library(${SELF_LIB}) # ---------------------------------------------------------------- From 020f72c375908e5ddb3b80b81c229efc25376499 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 15:13:55 -0400 Subject: [PATCH 0365/2524] build: track chnage to xo-ordinaltree cmake target name --- src/reactor/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reactor/CMakeLists.txt b/src/reactor/CMakeLists.txt index f65772c1..f84c1ca3 100644 --- a/src/reactor/CMakeLists.txt +++ b/src/reactor/CMakeLists.txt @@ -21,4 +21,4 @@ xo_dependency(${SELF_LIB} reflect) xo_dependency(${SELF_LIB} webutil) xo_dependency(${SELF_LIB} printjson) xo_dependency(${SELF_LIB} callback) -xo_dependency(${SELF_LIB} ordinaltree) +xo_dependency(${SELF_LIB} xo_ordinaltree) From 4e933b9a7c6a29ff86bf40e535cdb87cf59b0561 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 15:18:24 -0400 Subject: [PATCH 0366/2524] build: fix include paths --- CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d148b38e..f2a60a57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,8 +36,9 @@ add_subdirectory(utest) # ---------------------------------------------------------------- # header-only library -set(SELF_LIB xo_ordinaltree) -xo_add_headeronly_library(${SELF_LIB}) +set(SELF_SHORTNAME ordinaltree) +set(SELF_LIB xo_${SELF_SHORTNAME}) +xo_add_headeronly_library5(${SELF_LIB} ${SELF_SHORTNAME}) # ---------------------------------------------------------------- # From ff0c4f20198a517f70075525d29ecd13d291a6e8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 15:23:59 -0400 Subject: [PATCH 0367/2524] build: fix include-tree install --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f2a60a57..c6fcc268 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,8 +42,7 @@ xo_add_headeronly_library5(${SELF_LIB} ${SELF_SHORTNAME}) # ---------------------------------------------------------------- # -xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) -#xo_install_include_tree() +xo_install_library5(${SELF_LIB} ${SELF_SHORTNAME} ${PROJECT_NAME}Targets) # (note: ..Targets from xo_install_library2()) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) From 8ad546083132b44f362bc97011d873ab31610a5c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 15:50:42 -0400 Subject: [PATCH 0368/2524] build: patch xo- or xo_ prefixes --- cmake/xo_macros/xo_cxx.cmake | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 2ab628be..94a9cb27 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -247,6 +247,9 @@ endmacro() macro(xo_include_options2 target) xo_establish_submodule_build() + string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) + string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + #message("xo_include_options2: XO_SUBMODULE_BUILD=${XO_SUBMODULE_BUILD}") # ---------------------------------------------------------------- @@ -262,12 +265,12 @@ macro(xo_include_options2 target) target_include_directories( ${target} PUBLIC $ - $ - $ # e.g. for #include "indentlog/scope.hpp" - #$ # e.g. for #include "Refcounted.hpp" in refcnt/src [DEPRECATED] - $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect - #$ # e.g. for generated .hpp files - $ # e.g. for generated .hpp files + $ + $ # e.g. for #include "indentlog/scope.hpp" + #$ # e.g. for #include "Refcounted.hpp" in refcnt/src [DEPRECATED] + $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect + #$ # e.g. for generated .hpp files + $ # e.g. for generated .hpp files ) # ---------------------------------------------------------------- @@ -761,11 +764,14 @@ endmacro() # 2. pyfoo/pyfoo.hpp.in -> pyfoo/pyfoo.hpp # macro(xo_pybind11_library target projectTargets source_files) - file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/include/xo/${target}) + string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) + string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + + file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/include/xo/${_nxo_target}) configure_file( - ${target}.hpp.in - ${PROJECT_BINARY_DIR}/include/xo/${target}/${target}.hpp) + ${_nxo_target}.hpp.in + ${PROJECT_BINARY_DIR}/include/xo/${_nxo_target}/${_nxo_target}.hpp) # was ${PROJECT_SOURCE_DIR}/include/xo/${target}/${target}.hpp) xo_establish_symlink_install() @@ -774,12 +780,12 @@ macro(xo_pybind11_library target projectTargets source_files) xo_install_make_symlink( ${PROJECT_BINARY_DIR}/include/xo ${CMAKE_INSTALL_PREFIX}/include/xo - ${target}) + ${_nxo_target}) else() install( - FILES ${PROJECT_BINARY_DIR}/include/xo/${target}/${target}.hpp + FILES ${PROJECT_BINARY_DIR}/include/xo/${_nxo_target}/${_nxo_target}.hpp PERMISSIONS OWNER_READ GROUP_READ WORLD_READ - DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${target}) + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${_nxo_target}) endif() # find_package(Python..) finds python in From 098c979f2dfbc3ebce880f5ffdb874370d39dc04 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 15:53:04 -0400 Subject: [PATCH 0369/2524] cmake target pyreflect -> xo_pyreflect --- src/pyreflect/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyreflect/CMakeLists.txt b/src/pyreflect/CMakeLists.txt index 64be5e08..d4acdbfa 100644 --- a/src/pyreflect/CMakeLists.txt +++ b/src/pyreflect/CMakeLists.txt @@ -1,6 +1,6 @@ # xo_pyreflect/src/pyreflect/CMakeLists.txt -set(SELF_LIB pyreflect) +set(SELF_LIB xo_pyreflect) set(SELF_SRCS pyreflect.cpp) # ---------------------------------------------------------------- From a55be719146df74e3bf27bdb21750f91e2eb432d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 15:54:01 -0400 Subject: [PATCH 0370/2524] build: pyreflect -> xo_pyreflect --- src/pyprintjson/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyprintjson/CMakeLists.txt b/src/pyprintjson/CMakeLists.txt index d73baad1..ad68a137 100644 --- a/src/pyprintjson/CMakeLists.txt +++ b/src/pyprintjson/CMakeLists.txt @@ -6,4 +6,4 @@ set(SELF_SRCS pyprintjson.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} printjson) -xo_pybind11_header_dependency(${SELF_LIB} pyreflect) +xo_pybind11_header_dependency(${SELF_LIB} xo_pyreflect) From 5f9942cf0309961b8e90c6b19a48fddbacdaf22a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 16:18:45 -0400 Subject: [PATCH 0371/2524] build: bugfix: ordinaltree<-randomgen dependency in cmake config --- CMakeLists.txt | 3 ++- cmake/xo_ordinaltreeConfig.cmake.in | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6fcc268..745aacc9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- # input dependencies +# NOTE: dependency set here must be kept consistent with ordinaltree/cmake/xo_ordinaltreeConfig.cmake.in + # xo-ordinaltree is also header-only -# xo_headeronly_dependency(${SELF_LIB} randomgen) diff --git a/cmake/xo_ordinaltreeConfig.cmake.in b/cmake/xo_ordinaltreeConfig.cmake.in index 8fbf8cb5..7e308d14 100644 --- a/cmake/xo_ordinaltreeConfig.cmake.in +++ b/cmake/xo_ordinaltreeConfig.cmake.in @@ -1,5 +1,6 @@ @PACKAGE_INIT@ +include(CMakeFindDependencyMacro) +find_dependency(randomgen) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") - From 16306684d3c9c2b9433f23863f5bc851df4ce83d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:05:05 -0400 Subject: [PATCH 0372/2524] cosmetic: comments --- cmake/xo_macros/xo_cxx.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 94a9cb27..27e3d02c 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -834,7 +834,7 @@ endmacro() # use this for a dependency of a pybind11 library, # e.g. that was introduced by xo_pybind11_library() # -# Working around the following problem (cmake 3.25.3, pybind11 2.10.4).N +# Working around the following problem (cmake 3.25.3, pybind11 2.10.4) # if: # 1. we have pybind11 library pyfoo, depending on c++ native library foo. # 2. foo depends on other libraries foodep1, foodep2; @@ -849,7 +849,7 @@ endmacro() # get compile instructions like: # g++ -o pyfoo.cpython-311-x86_64-linux-gnu.so path/to/pyfoo.cpp.o path/to/libfoo.so.x.y -lfoodep1 -lfoodep2 # -# 1. This is broken for foodep2, since there no libfoodep2.so exists +# 1. This is broken for foodep2, since in this case no libfoodep2.so exists # 2. Also broken for nix build, because directory containing libfoodep1.so doesn't appear on the compile line. # (It's likely possible to extract this from the .cmake package in lib/cmake/foo/fooTargets.cmake, # but I don't know how to do that yet) @@ -871,6 +871,8 @@ macro(xo_pybind11_dependency target dep) else() message("xo_pybind11_dependency: ${target}: clobbering ${dep}.INTERFACE_LINK_LIBRARIES") set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") + + # also have to clobber libraries for endif() # now that secondary deps are gone, attach to target pybind11 library # skip xo_dependency() here, that would repeat the find_package() expansion From 24a145b48936688a4493a09b6e29d1ba0abde249 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:05:37 -0400 Subject: [PATCH 0373/2524] cosmetic: warning comment --- src/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4622ad44..1b90ed07 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,4 +2,8 @@ set(SELF_LIB refcnt) set(SELF_SRCS Refcounted.cpp Displayable.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# NOTE: +# dependency set here must be kept consistent with refcnt/cmake/refcntConfig.cmake.in +# xo_dependency(${SELF_LIB} indentlog) From 4ccb726cca80ebaee41056a87fb24191aa47e66f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:26:00 -0400 Subject: [PATCH 0374/2524] xo-cmake: simplify api -- drop nxo_ args --- cmake/xo_macros/xo_cxx.cmake | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 27e3d02c..80429258 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -65,9 +65,11 @@ endmacro() # e.g. # - xo_target = xo_pyutil -# - nxo_target = pyutil # -macro(xo_include_headeronly_options5 target nxo_target) +macro(xo_include_headeronly_options target) + string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) + string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + # ---------------------------------------------------------------- # PROJECT_SOURCE_DIR: # so we can for example write @@ -84,12 +86,10 @@ macro(xo_include_headeronly_options5 target nxo_target) target_include_directories( ${target} INTERFACE $ - $ - $ # e.g. for #include "indentlog/scope.hpp" - #$ # e.g. for #include "Refcounted.hpp" in refcnt/src when ${target}=refcnt [DEPRECATED] - $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect - #$ # e.g. for generated .hpp files - $ # e.g. for generated .hpp files + $ + $ # e.g. for #include "indentlog/scope.hpp" + $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect + $ # e.g. for generated .hpp files ) # ---------------------------------------------------------------- @@ -104,7 +104,7 @@ macro(xo_include_headeronly_options5 target nxo_target) endmacro() macro(xo_include_headeronly_options2 target) - xo_include_headeronly_options5(${target} ${target}) + xo_include_headeronly_options(${target}) endmacro() # ---------------------------------------------------------------- @@ -205,9 +205,8 @@ endmacro() # # e.g. # - target=xo_pyutil cmake target name for this library -# - nxo_target=pyutil directory for this target under include/xo # -macro(xo_add_headeronly_library5 target nxo_target) +macro(xo_add_headeronly_library target) add_library(${target} INTERFACE) set_property( @@ -220,11 +219,7 @@ macro(xo_add_headeronly_library5 target nxo_target) TARGET ${target} PROPERTY xo_bindir ${PROJECT_BINARY_DIR}) - xo_include_headeronly_options5(${target} ${nxo_target}) -endmacro() - -macro(xo_add_headeronly_library target) - xo_add_headeronly_library5(${target} ${target}) + xo_include_headeronly_options(${target}) endmacro() # ---------------------------------------------------------------- From ef1e0b037135af6129f5214d835af05a9f7b71fc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:26:35 -0400 Subject: [PATCH 0375/2524] build: use streamlined xo-cmake api --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 745aacc9..263db2ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,7 +38,7 @@ add_subdirectory(utest) set(SELF_SHORTNAME ordinaltree) set(SELF_LIB xo_${SELF_SHORTNAME}) -xo_add_headeronly_library5(${SELF_LIB} ${SELF_SHORTNAME}) +xo_add_headeronly_library(${SELF_LIB}) # ---------------------------------------------------------------- # From 6f1b0aca760a719a173fe4a60ba97c8c798cd8e5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:27:05 -0400 Subject: [PATCH 0376/2524] build: use streamlined xo-cmake api --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f85dd952..b5894f9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ xo_toplevel_compile_options() set(SELF_SHORTNAME pyutil) set(SELF_LIB xo_${SELF_SHORTNAME}) -xo_add_headeronly_library5(${SELF_LIB} ${SELF_SHORTNAME}) +xo_add_headeronly_library(${SELF_LIB}) #xo_include_headeronly_options2(${SELF_LIB}) # ---------------------------------------------------------------- From b2d7a04a09f9224618d96c3faf2da638a11ddeee Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:36:21 -0400 Subject: [PATCH 0377/2524] build: extend xo_install_library4 + retire xo_install_library5P --- cmake/xo_macros/xo_cxx.cmake | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 80429258..a90820f7 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -404,6 +404,9 @@ macro(xo_install_library3 target projectTargets) endmacro() macro(xo_install_library4 target projectTargets) + string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) + string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + install( TARGETS ${target} EXPORT ${projectTargets} @@ -414,7 +417,7 @@ macro(xo_install_library4 target projectTargets) BUNDLE DESTINATION bin COMPONENT Runtime ) - xo_install_include_tree3(include/xo/${target}) + xo_install_include_tree3(include/xo/${_nxo_target}) #xo_install_include_tree() -- use xo_install_include_tree3() separately endmacro() @@ -431,22 +434,6 @@ macro(xo_install_library4_noincludes target projectTargets) ) endmacro() -macro(xo_install_library5 target nxo_target projectTargets) - install( - TARGETS ${target} - EXPORT ${projectTargets} - 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 - ) - - xo_install_include_tree3(include/xo/${nxo_target}) - - #xo_install_include_tree() -- use xo_install_include_tree3() separately -endmacro() - # ---------------------------------------------------------------- # for projectname=foo, require: From f880bc66598db2b9fcb57cd6ca3c0b67edd561a6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:37:10 -0400 Subject: [PATCH 0378/2524] build: simplify cmake macro api --- CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b5894f9a..8b8e6720 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,15 +50,14 @@ xo_toplevel_compile_options() # ---------------------------------------------------------------- # output targets -set(SELF_SHORTNAME pyutil) -set(SELF_LIB xo_${SELF_SHORTNAME}) +set(SELF_LIB xo_pyutil) xo_add_headeronly_library(${SELF_LIB}) #xo_include_headeronly_options2(${SELF_LIB}) # ---------------------------------------------------------------- # standard install + provide find_package() support -xo_install_library5(${SELF_LIB} ${SELF_SHORTNAME} ${PROJECT_NAME}Targets) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) #xo_install_include_tree() xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) From b5dc259687ff5a19dcf49a57c4a8edac56ed6f0d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:37:39 -0400 Subject: [PATCH 0379/2524] build: use simplified cmake macro api --- CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 263db2ca..f336712c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,13 +36,12 @@ add_subdirectory(utest) # ---------------------------------------------------------------- # header-only library -set(SELF_SHORTNAME ordinaltree) -set(SELF_LIB xo_${SELF_SHORTNAME}) +set(SELF_LIB xo_ordinaltree) xo_add_headeronly_library(${SELF_LIB}) # ---------------------------------------------------------------- # -xo_install_library5(${SELF_LIB} ${SELF_SHORTNAME} ${PROJECT_NAME}Targets) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) # (note: ..Targets from xo_install_library2()) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) From 6360cd7297d726534a9b1402343804360ccb85a4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:45:52 -0400 Subject: [PATCH 0380/2524] build: + xo_strip_xo_prefix() + use to consolidate --- cmake/xo_macros/xo_cxx.cmake | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index a90820f7..cba39ed2 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -63,12 +63,20 @@ macro(xo_toplevel_compile_options) endif() endmacro() +# xo_strip_xo_prefix(xo_foo tmp) --> tmp=foo +# xo_strip_xo_prefix(xo-foo tmp) --> tmp=foo +# xo_strip_xo_prefix(foo tmp) --> tmp=foo +# +macro(xo_strip_xo_prefix str outputvar) + string(REGEX REPLACE "^xo_" "" _tmp ${str}) + string(REGEX REPLACE "^xo-" "" ${outputvar} ${_tmp}) +endmacro() + # e.g. # - xo_target = xo_pyutil # macro(xo_include_headeronly_options target) - string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) - string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + xo_strip_xo_prefix(${target} _nxo_target) # ---------------------------------------------------------------- # PROJECT_SOURCE_DIR: @@ -242,8 +250,7 @@ endmacro() macro(xo_include_options2 target) xo_establish_submodule_build() - string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) - string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + xo_strip_xo_prefix(${target} _nxo_target) #message("xo_include_options2: XO_SUBMODULE_BUILD=${XO_SUBMODULE_BUILD}") @@ -404,8 +411,7 @@ macro(xo_install_library3 target projectTargets) endmacro() macro(xo_install_library4 target projectTargets) - string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) - string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + xo_strip_xo_prefix(${target} _nxo_target) install( TARGETS ${target} @@ -505,8 +511,7 @@ endmacro() # nxo_dep: cmake target without any xo_ prefix. (e.g. pyutil) # macro(xo_dependency_helper target visibility dep) - string(REGEX REPLACE "^xo_" "" _nxo_dep ${dep}) - string(REGEX REPLACE "^xo-" "" _nxo_dep ${_nxo_dep}) + xo_strip_xo_prefix(${dep} _nxo_dep) xo_establish_submodule_build() @@ -746,8 +751,7 @@ endmacro() # 2. pyfoo/pyfoo.hpp.in -> pyfoo/pyfoo.hpp # macro(xo_pybind11_library target projectTargets source_files) - string(REGEX REPLACE "^xo_" "" _nxo_target ${target}) - string(REGEX REPLACE "^xo-" "" _nxo_target ${_nxo_target}) + xo_strip_xo_prefix(${target} _nxo_target) file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/include/xo/${_nxo_target}) From cd969000466a7b6a91c7fab806f0db70635032ee Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 17:52:50 -0400 Subject: [PATCH 0381/2524] build: drop debug log messages --- cmake/xo_macros/xo_cxx.cmake | 45 ++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index cba39ed2..2f9560db 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -528,15 +528,15 @@ macro(xo_dependency_helper target visibility dep) endif() if (XO_SUBMODULE_BUILD) - get_target_property(_tmp ${target} LINK_LIBRARIES) - message("xo_dependency_helper: ${target} -> ${dep}: ${target}.LINK_LIBRARIES=${_tmp}") - get_target_property(_tmp ${dep} LINK_LIBRARIES) - message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.LINK_LIBRARIES=${_tmp}") + #get_target_property(_tmp ${target} LINK_LIBRARIES) + #message("xo_dependency_helper: ${target} -> ${dep}: ${target}.LINK_LIBRARIES=${_tmp}") + #get_target_property(_tmp ${dep} LINK_LIBRARIES) + #message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.LINK_LIBRARIES=${_tmp}") - get_target_property(_tmp ${target} INTERFACE_LINK_LIBRARIES) - message("xo_dependency_helper: ${target} -> ${dep}: ${target}.INTERFACE_LINK_LIBRARIES=${_tmp}") - get_target_property(_tmp ${dep} INTERFACE_LINK_LIBRARIES) - message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.INTERFACE_LINK_LIBRARIES=${_tmp}") + #get_target_property(_tmp ${target} INTERFACE_LINK_LIBRARIES) + #message("xo_dependency_helper: ${target} -> ${dep}: ${target}.INTERFACE_LINK_LIBRARIES=${_tmp}") + #get_target_property(_tmp ${dep} INTERFACE_LINK_LIBRARIES) + #message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.INTERFACE_LINK_LIBRARIES=${_tmp}") # add INCLUDE_DIRECTORIES from ${dep} to ${target}. # @@ -554,7 +554,7 @@ macro(xo_dependency_helper target visibility dep) get_target_property(_depbindir ${dep} xo_bindir) get_target_property(_tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) if(_tmp) - message("xo_dependency_helper: ${target}: + ${dep}.INCLUDE_DIRECTORIES: ${_tmp}") + #message("xo_dependency_helper: ${target}: + ${dep}.INCLUDE_DIRECTORIES: ${_tmp}") foreach(dir ${_tmp}) # want to add these to compile line for $target}. # this will happen automatically for ${dep}'s own directories; @@ -563,21 +563,16 @@ macro(xo_dependency_helper target visibility dep) # however we also need directories for ${dep}'s transitive dependencies # if(${dir} MATCHES "BUILD_INTERFACE") - message("xo_dependency_helper: ${target} -> ${dep}: consider dir=${dir}") + #message("xo_dependency_helper: ${target} -> ${dep}: consider dir=${dir}") if(${dir} MATCHES ${_depsrcdir}) #message(" skip ${dir}") elseif(${dir} MATCHES ${_depbindir}) #message(" skip ${dir}") else() - message(" KEEP ${dir}") + #message(" KEEP ${dir}") target_include_directories(${target} ${visibility} ${dir}) endif() endif() - -# set_property( -# TARGET ${target} -# APPEND -# PROPERTY INCLUDE_DIRECTORIES ${dir}) endforeach() get_target_property(_tmp ${target} INTERFACE_INCLUDE_DIRECTORIES) list(REMOVE_DUPLICATES _tmp) @@ -625,13 +620,13 @@ macro(xo_dependency target dep) xo_dependency_helper(${target} PUBLIC ${dep}) - message("----------------------------------------------------------------") - #message("xo_dependency: ${target}.xo_deps.pre=${_xo_dependency_tmp0}") - #message("xo_dependency: ${dep}.xo_deps=${_xo_dependency_tmp2}") - get_target_property(_tmp ${target} xo_deps) - message("xo_dependency: ${target}.xo_deps=${_tmp}") - #get_target_property(_tmp ${target} INCLUDE_DIRECTORIES) - #message("xo_dependency: ${target}.INCLUDE_DIRECTORIES=${_tmp} before target_link_libraries with ${dep}") + #message("----------------------------------------------------------------") + ##message("xo_dependency: ${target}.xo_deps.pre=${_xo_dependency_tmp0}") + ##message("xo_dependency: ${dep}.xo_deps=${_xo_dependency_tmp2}") + #get_target_property(_tmp ${target} xo_deps) + #message("xo_dependency: ${target}.xo_deps=${_tmp}") + ##get_target_property(_tmp ${target} INCLUDE_DIRECTORIES) + ##message("xo_dependency: ${target}.INCLUDE_DIRECTORIES=${_tmp} before target_link_libraries with ${dep}") target_link_libraries(${target} PUBLIC ${dep}) #target_link_libraries(${target} ${dep}) @@ -642,7 +637,7 @@ macro(xo_dependency target dep) #message("xo_dependency: ${dep}.INCLUDE_DIRECTORIES=${_tmp}") #get_target_property(_tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) #message("xo_dependency: ${dep}.INTERFACE_INCLUDE_DIRECTORIES=${_tmp}") - message("----------------------------------------------------------------") + #message("----------------------------------------------------------------") endmacro() # dependency of a header-only library on another header-only library @@ -853,7 +848,7 @@ macro(xo_pybind11_dependency target dep) # clobber secondary dependencies, as discussed above if(XO_SUBMODULE_BUILD) # ok to keep dep libraries on link line in submodule build - message("xo_pybind11_dependency: ${target}: don't clobber ${dep}.INTERFACE_LINK_LIBRARIES") + #message("xo_pybind11_dependency: ${target}: don't clobber ${dep}.INTERFACE_LINK_LIBRARIES") else() message("xo_pybind11_dependency: ${target}: clobbering ${dep}.INTERFACE_LINK_LIBRARIES") set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") From db01f8cefc350e9053f9f01cff9909e966e059a4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 20:26:27 -0400 Subject: [PATCH 0382/2524] bugfix: varname in .hpp template --- src/pyreactor/pyreactor.hpp.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pyreactor/pyreactor.hpp.in b/src/pyreactor/pyreactor.hpp.in index edbb2e78..140ded1b 100644 --- a/src/pyreactor/pyreactor.hpp.in +++ b/src/pyreactor/pyreactor.hpp.in @@ -8,18 +8,18 @@ * example: * PYBIND11_MODULE(PYREACTOR_MODULE_NAME(), m) { ... } */ -#define PYREACTOR_MODULE_NAME() @SELF_LIBRARY_NAME@ +#define PYREACTOR_MODULE_NAME() @SELF_LIB@ /* example: * py::module_::import(PYREACTOR_MODULE_NAME_STR) */ -#define PYREACTOR_MODULE_NAME_STR "@SELF_LIBRARY_NAME@" +#define PYREACTOR_MODULE_NAME_STR "@SELF_LIB@" /* example: * PYREACTOR_IMPORT_MODULE() * replaces * py::module_::import("pyreactor") */ -#define PYREACTOR_IMPORT_MODULE() py::module_::import("@SELF_LIBRARY_NAME@") +#define PYREACTOR_IMPORT_MODULE() py::module_::import("@SELF_LIB@") /* end pyreactor.hpp */ From 8454b48956dcaf7b8884588595ef52d230067b4f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 22 Oct 2023 20:51:11 -0400 Subject: [PATCH 0383/2524] initial implementation --- CMakeLists.txt.old | 37 ++++ EXAMPLES | 164 +++++++++++++++ EigenUtil.cpp | 157 ++++++++++++++ EigenUtil.hpp | 29 +++ KalmanFilter.cpp | 78 +++++++ KalmanFilter.hpp | 128 ++++++++++++ KalmanFilterEngine.cpp | 360 +++++++++++++++++++++++++++++++++ KalmanFilterEngine.hpp | 185 +++++++++++++++++ KalmanFilterInput.cpp | 118 +++++++++++ KalmanFilterInput.hpp | 121 +++++++++++ KalmanFilterInputCallback.hpp | 14 ++ KalmanFilterInputSource.hpp | 28 +++ KalmanFilterInputToConsole.cpp | 25 +++ KalmanFilterInputToConsole.hpp | 30 +++ KalmanFilterObservable.cpp | 71 +++++++ KalmanFilterObservable.hpp | 110 ++++++++++ KalmanFilterOutputCallback.hpp | 15 ++ KalmanFilterSpec.cpp | 27 +++ KalmanFilterSpec.hpp | 86 ++++++++ KalmanFilterState.cpp | 231 +++++++++++++++++++++ KalmanFilterState.hpp | 163 +++++++++++++++ KalmanFilterStateToConsole.cpp | 25 +++ KalmanFilterStateToConsole.hpp | 30 +++ KalmanFilterStep.cpp | 84 ++++++++ KalmanFilterStep.hpp | 128 ++++++++++++ KalmanFilterSvc.cpp | 44 ++++ KalmanFilterSvc.hpp | 64 ++++++ KalmanFilterTransition.cpp | 55 +++++ KalmanFilterTransition.hpp | 62 ++++++ init_filter.cpp | 51 +++++ init_filter.hpp | 20 ++ print_eigen.hpp | 41 ++++ 32 files changed, 2781 insertions(+) create mode 100644 CMakeLists.txt.old create mode 100644 EXAMPLES create mode 100644 EigenUtil.cpp create mode 100644 EigenUtil.hpp create mode 100644 KalmanFilter.cpp create mode 100644 KalmanFilter.hpp create mode 100644 KalmanFilterEngine.cpp create mode 100644 KalmanFilterEngine.hpp create mode 100644 KalmanFilterInput.cpp create mode 100644 KalmanFilterInput.hpp create mode 100644 KalmanFilterInputCallback.hpp create mode 100644 KalmanFilterInputSource.hpp create mode 100644 KalmanFilterInputToConsole.cpp create mode 100644 KalmanFilterInputToConsole.hpp create mode 100644 KalmanFilterObservable.cpp create mode 100644 KalmanFilterObservable.hpp create mode 100644 KalmanFilterOutputCallback.hpp create mode 100644 KalmanFilterSpec.cpp create mode 100644 KalmanFilterSpec.hpp create mode 100644 KalmanFilterState.cpp create mode 100644 KalmanFilterState.hpp create mode 100644 KalmanFilterStateToConsole.cpp create mode 100644 KalmanFilterStateToConsole.hpp create mode 100644 KalmanFilterStep.cpp create mode 100644 KalmanFilterStep.hpp create mode 100644 KalmanFilterSvc.cpp create mode 100644 KalmanFilterSvc.hpp create mode 100644 KalmanFilterTransition.cpp create mode 100644 KalmanFilterTransition.hpp create mode 100644 init_filter.cpp create mode 100644 init_filter.hpp create mode 100644 print_eigen.hpp diff --git a/CMakeLists.txt.old b/CMakeLists.txt.old new file mode 100644 index 00000000..e85d245f --- /dev/null +++ b/CMakeLists.txt.old @@ -0,0 +1,37 @@ +# filter/CMakeLists.txt + +set(SELF_LIBRARY_NAME filter) + +set(SELF_SOURCE_FILES KalmanFilterSvc.cpp KalmanFilter.cpp KalmanFilterState.cpp KalmanFilterTransition.cpp KalmanFilterObservable.cpp KalmanFilterInput.cpp KalmanFilterStep.cpp KalmanFilterSpec.cpp KalmanFilterEngine.cpp KalmanFilterInputToConsole.cpp KalmanFilterStateToConsole.cpp EigenUtil.cpp init_filter.cpp) + +# build shared liburary 'filter' +add_library(${SELF_LIBRARY_NAME} SHARED ${SELF_SOURCE_FILES}) + +set_target_properties(${SELF_LIBRARY_NAME} + PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION 1 + PUBLIC_HEADER init_filter.hpp) + +# ---------------------------------------------------------------- +# all the warnings! +# +xo_compile_options(${SELF_LIBRARY_NAME}) +xo_include_options(${SELF_LIBRARY_NAME}) + +# ---------------------------------------------------------------- +# internal dependencies: reactor, ... + +#target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC process) +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC reactor) +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC printjson) +target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) + +# ---------------------------------------------------------------- +# 3rd party dependency: eigen: + +xo_eigen_dependency(${SELF_LIBRARY_NAME}) + +xo_install_library(${SELF_LIBRARY_NAME}) + +# end CMakeLists.txt diff --git a/EXAMPLES b/EXAMPLES new file mode 100644 index 00000000..8f69ea73 --- /dev/null +++ b/EXAMPLES @@ -0,0 +1,164 @@ +scaffold kalman filter implementation here + +notation: + +x_(k) : n x 1 : true system state that we want to estimate +F(k) : n x n : state transition matrix at time t(k) +w_(k) : n x 1 : system noise, with mean 0, covariance Q(k) +Q(k) : n x n : covariance of systen noise + +v_(k) : m x 1 : observation errors at time t(k) +R(k) : m x m : observation error covariance matrix at time t(k) + +x(k) : n x 1 : state vector estimate for time t(k) +P(k) : n x n : covariance matrix for x(k) +z(k) : m x 1 : observation vector at time t(k) +H(k) : m x n : observation matrix at time t(k) + +K(k) : : kalman gain - measures information gain from observation z(k) + relative to prior P(k | k-1) + +use shorthand: + xT, F(k)T for transpose(x), transpose(F(k)) + x(k+1 | k) for "estimate x(k+1) given information known at t(k)" + +A. Linear Kalman Filter +----------------------- + +1. system model: + x_(k+1) = F(k).x_(k) + w_(k), w_(k) ~ N(0,Q) + + i.e. expected behavior of system from t(k) -> t(k+1), + absent system noise, is given by linear transformation F(k) + + ofc model is not directly observable, + since we don't know x_(k) or w_(k), + instead we will be estimating it. + +2. measurement model: + z(k+1) = H(k+1).x_(k+1) + v_(k), v_(k) ~ N(0, R) + +3. prior: + x(0), P(0) + + must be supplied as initial input + +4. pre-estimate for t(k+1) system state: + (before incorporating z(k+1) and accounting for system noise) + x(k+1|k) := F(k).x(k) + + in other words propagate t(k) estimate to t(k+1), + using F(k) + +5. pre-estimate for t(k+1) estimate covariance + (before incorporating z(k+1) and accounting for system noise) + P(k+1|k) := F(k).P(k).F(k)T + Q(k) + +6. kalman gain matrix + K(k+1) := P(k+1|k).H(k)T.inverse(H(k).P(k+1|k).H(k)T + R(k)) + + note that the matrix-to-be-inverted in the gain expression + is symmetric and positive-definite (so can use cholesky decomposition) + +7. innovation: difference between actual observation vector and + observation predicted from state estimate x(k+1|k): + z(k+1) - H(k+1).x(k+1|k) + +8. corrected state estimate for t(k+1): + x(k+1) := x(k+1|k) + K(k+1)[z(k+1) - H(k+1).x(k+1|k)] + +9. correct state covariance for t(k+1): + P(k+1) := [I - K(k+1).H(k+1)].P(k+1|k) + + +B. Extended Kalman Filter +------------------------- +Must write in continuous form, to deal with non-linearities + +notation: + +x_(t) : n x 1 : true system state that we want to estimate, + continuously evolves with t +f(x_(t),t) : n x 1 : expected derivative (d/dt)(x_(t)) +h(x_(t),t) : m x 1 : observation function +x(k) : n x 1 : state vector estimate for x(t) at time t=t(k) +F(x(t),t) : n x n : jacobian of f(x_(t),t) evaluated at x_(t)=x(t) +H(x(t),t) : m x n : jacobian of h(x_(t),t) evaluated at x_(t)=x(t) + +1. system model: + (d/dt)x_(t) = f(x_(t),t) + w(t), + where + w(t) is a white noise with + + / t2 + | + | w(t).dt ~ N(0, Q(t1,t2)) + | + / t1 + + editorial: + note that we exclude more general model + (d/dt)x_(t) = f(x_(t),t)) + G(x_(t),t).w(t) + because would be unable to assume RHS mean-square integrable. + The form chosen effectively band-limits the frequencies at which + w(t) acts, since it's independent of x_(t). This allows mean-square + rules to be applied as in linear kalman filter + +2. measurement model: + z(k+1) = h(x_(t(k+1)), t(k+1)) + v(k), v(k) ~ N(0, Rk) + +3. prior: + x(0), P(0) + + (same as for linear kalman filter) + +(3a). state estimate propagation model: + (d/dt)x(t) = f(x(t),t) + + editorial: + this step is *sketchy*: x(t) is intended to be minimum-variance + estimate for system state, but that property is lost when solving + differential equation (d/dt)x(t) = f(x(t),t) with non-linear f. + +(3b). covariance estimate propagation model: + (d/dt)P(t) = F(x(t),t).P(t) + P(t).F(x(t),t)T + Q(t) + +4. pre-estimate (extrapolation) for t(k+1) system state: + + can use first order approx: + x(k+1|k) = x(k) + F(x(tk),tk).(t(k+1) - t(k)) + or multiple timesteps if t(k+1) - t(k) is too large + + i.e. approximately solving (3a) + +5. pre-estimate (extrapolation) for t(k+1) error covariance: + + P(k+1|k) = P(k) + [F(x(tk),tk).P(k) + P(k)F(x(tk),tk)T + Q(tk)].(t(k+1) - t(k)) + or multiple timesteps if t(k+1) - t(k) is too large + + i.e. approximately solving (3b) + +6. kalman gain matrix + K(k+1) = P(k+1|k) + .H(k+1)T(x(k+1|k)) + .inverse[H(k+1)(x(k+1|k)).P(k+1|k).H(k+1)T(x(k+1|k)) + R(k+1)] + +7. innovation: + z(k+1) - h(x(k+1|k), t(k+1)) + +8. corrected state estimate for t(k+1): + x(k+1) = x(k+1|k) + K(k+1).(z(k+1) - h(k+1)(x(k+1|k)) + +9. corrected error covariance for t(k+1): + P(k+1) = [I - K(k+1).H(k+1)(x(k+1|k))].P(k+1|k) + + editorial: + This step is *sketchy*, to the extend h() is non-linear, linearizing around + x(k+1|k) may give poor estimate of state covariance + +10. linearization (1st order term for taylor series of f() around x(t)) + jacobian: + F(x(t),t) = (df/dx_) evaluated at x_(t)=x(t) + + H(k+1)(x(k+1|k)) = (dh/dx_) evaluated at x_(t)=x(k+1|k) + diff --git a/EigenUtil.cpp b/EigenUtil.cpp new file mode 100644 index 00000000..f1d3c4a3 --- /dev/null +++ b/EigenUtil.cpp @@ -0,0 +1,157 @@ +/* file EigenUtil.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "EigenUtil.hpp" +#include "printjson/PrintJson.hpp" +#include "reflect/Reflect.hpp" +#include +#include +#include +#include + +namespace xo { + using xo::json::PrintJson; + using xo::json::JsonPrinter; + using xo::reflect::Reflect; + using xo::reflect::TypeDescr; + using VectorXb = Eigen::Array; + using Eigen::VectorXd; + using Eigen::MatrixXd; + +#ifdef NOT_YET + namespace reflect { + template + using EigenVectorX_Tdx = xo::reflect::StlVectorTdx>; + + /* probably need this to appear before decl for class xo::reflect::Reflect */ + template + class EstablishTdx> { + public: + static std::unique_ptr make() { + return EigenVectorX_Tdx::make(); + } /*make*/ + }; /*EstablishTdx*/ + } /*reflect*/ +#endif + + namespace eigen { + + namespace { + /* prints a VectorXd as json, in the obvious format, e.g. + * [1,2,3] + */ + template + class EigenVectorJsonPrinter : public JsonPrinter { + public: + EigenVectorJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override + { + EigenVectorType * pv = this->check_recover_native(tp, p_os); + + if (pv) { + /* EigenVectorType (VectorXb, VectorXd, ..) + * is reflected as atomic for now, out of expedience. + * + * as soon as we reflect as mt_vector, will not need this helper. + */ + *p_os << "["; + + for (std::uint32_t i = 0, n = pv->size(); i < n; ++i) { + if (i > 0) + *p_os << ","; + + /* note: need to dispatch via json printer for vector elements, + * to get special treatment for non-finite values + */ + this->pjson()->print((*pv)[i], p_os); + //*p_os << jsonp((*pv)[i], this->pjson()); + } + + *p_os << "]"; + } + } /*print_json*/ + }; /*EigenVectorJsonPrinter*/ + + /* prints a MatrixXd as json, in row-major format, e.g. + * [[1,2,3], [4,5,6], [7,8,9]] + */ + class MatrixXdJsonPrinter : public JsonPrinter { + public: + MatrixXdJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override + { + MatrixXd * pm = this->check_recover_native(tp, p_os); + + if (pm) { + /* MatrixXd is reflected as atomic for now, out of expedience */ + *p_os << "["; + + for(std::uint32_t i=0, m=pm->rows(); i 0) + *p_os << ", "; + *p_os << "["; + for(std::uint32_t j=0, n=pm->cols(); j 0) + *p_os << ","; + + /* note: need to dispatch via json printer for matrix elements, + * to get special treatment for non-finite values + */ + this->pjson()->print((*pm)(i, j), p_os); + //*p_os << jsonp((*pm)(i, j), this->pjson()); + } + *p_os << "]"; + } + + *p_os << "]"; + } + } /*print_json*/ + }; /*MatrixXdJsonPrinter*/ + + template + void + provide_eigen_vector_printer(PrintJson * p_pjson) + { + TypeDescr td = Reflect::require(); + std::unique_ptr pr(new EigenVectorJsonPrinter(p_pjson)); + + p_pjson->provide_printer(td, std::move(pr)); + } /*provide_eigen_vector_printer*/ + } /*namespace*/ + + void + EigenUtil::reflect_eigen() + { +#ifdef NOT_YET + Reflect::require(); + Reflect::require(); +#endif + } /*reflect_eigen*/ + + void + EigenUtil::provide_json_printers(PrintJson * p_pjson) + { + assert(p_pjson); + + provide_eigen_vector_printer(p_pjson); + provide_eigen_vector_printer(p_pjson); + + { + TypeDescr td = Reflect::require(); + std::unique_ptr pr(new MatrixXdJsonPrinter(p_pjson)); + + p_pjson->provide_printer(td, std::move(pr)); + } + } /*provide_json_printers*/ + } /*namespace eigen*/ +} /*namespace xo*/ + +/* end EigenUtil.cpp */ diff --git a/EigenUtil.hpp b/EigenUtil.hpp new file mode 100644 index 00000000..07a86d48 --- /dev/null +++ b/EigenUtil.hpp @@ -0,0 +1,29 @@ +/* file EigenUtil.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +namespace xo { + namespace json { class PrintJson; } + + namespace eigen { + class EigenUtil { + public: + /* reflection for + * Eigen::VectorXd + * VectorXb (= Eigen::Array; by analogy with Eigen::VectorXd) + */ + static void reflect_eigen(); + + /* json printers for: + * Eigen::VectorXd + * Eigen::MatrixXd + */ + static void provide_json_printers(json::PrintJson * pjson); + }; /*EigenUtil*/ + } /*namespace eigen*/ +} /*namespace xo*/ + +/* end EigenUtil.hpp */ diff --git a/KalmanFilter.cpp b/KalmanFilter.cpp new file mode 100644 index 00000000..bf94fde2 --- /dev/null +++ b/KalmanFilter.cpp @@ -0,0 +1,78 @@ +/* @file KalmanFilter.cpp */ + +#include "KalmanFilter.hpp" +#include "KalmanFilterEngine.hpp" +#include "print_eigen.hpp" +#include "indentlog/scope.hpp" +#include "Eigen/src/Core/Matrix.h" + +namespace xo { + using xo::time::utc_nanos; + //using logutil::matrix; + using xo::scope; + using xo::tostr; + using xo::xtag; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + // ----- KalmanFilter ----- + + KalmanFilter::KalmanFilter(KalmanFilterSpec spec) + : filter_spec_{std::move(spec)}, + state_ext_{filter_spec_.start_ext()} + {} /*ctor*/ + + void + KalmanFilter::notify_input(ref::rp const & input_kp1) + { + scope log(XO_ENTER0(info)); + + /* on entry: + * .state_ext refers to t(k) + * on exit: + * .step refers to t(k+1) + * .state_ext refers to t(k+1) + */ + + log && log(xtag("step_dt", + input_kp1->tkp1() - this->state_ext_->tm())); + + /* establish step inputs for this filter step: + * F(k+1) (system transition matrix) + * Q(k+1) (system noise covariance matrix) + * H(k+1) (observation coupling matrix) + * R(k+1) (observation noise covariance matrix) + * z(k+1) (observation vector) + */ + this->step_ = this->filter_spec_.make_step(this->state_ext_, input_kp1); + + //if (lscope.enabled()) { lscope.log(xtag("step", this->step_)); } + + /* extrapolate filter state to t(k+1), + * and correct based on z(k+1) + */ + this->state_ext_ = KalmanFilterEngine::step(this->step_); + + //if (lscope.enabled()) { lscope.log(xtag("state_ext", this->state_ext_)); } + } /*notify_input*/ + + void + KalmanFilter::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilter::display_string() const + { + return tostr(*this); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilter.cpp */ diff --git a/KalmanFilter.hpp b/KalmanFilter.hpp new file mode 100644 index 00000000..295a4f7a --- /dev/null +++ b/KalmanFilter.hpp @@ -0,0 +1,128 @@ +/* @file KalmanFilter.hpp */ + +#pragma once + +#include "filter/KalmanFilterSpec.hpp" + +namespace xo { + namespace kalman { + /* Specification for an ordinary discrete linear kalman filter. + * + * The filter generates estimates for a process observed at a discrete + * set of times tk in {t0, t1, .., tn} + * + * At each time tk we have the following: + * + * 0. x(0) initial estimate at t(0) + * P(0) initial priors: error covariance matrix for x(0) + * + * 1. x_(k), [n x 1] vector: + * system state, denoted by vector. + * (state is not directly observable, + * filter will attempt to estimate it) + * + * 2. w_(k), [n x 1] vector + * Q(k), [n x n] matrix + * + * w_(k) denotes system noise, + * gaussian with covariance Q(k). + * noise w_(k) is not directly observable. + * + * 3. z(k), [m x 1] vector: + * + * observation vector for time tk + * + * 4. v_(k), [m x 1] vector + * R(k), [m x m] matrix + * + * v_(k) denotes observation errors, + * gaussian with covariance R(k). + * noise v_(k) is not directly observable. + * + * 5. F(k), [n x n] matrix + * state transition matrix + * model system evolves according to: + * + * x_(k+1) = F(x).x_(k) + w_(k) + * + * 6. observations z(k) depend on system state: + * + * z(k) = H(k).x_(k) + v_(k) + * + * 7. Kalman filter outputs: + * x(k), [n x 1] vector + * Q(k), [n x n] matrix + * + * x(k) is optimal estimate for system state x_(k) + * P(k) is covariance matrix specifying confidence intervals + * for pairs (x(k)[i], x(k)[j]) + * + * filter specification consists of: + * n, m, x(0), P(0), F(k), Q(k), H(k), R(k) + * The cardinality of observations z(k) can vary over time, + * so to be precise, m can vary with tk; write as m(k) + * + * More details: + * - avoid having to specify t(k) in advance; + * instead defer until observation available + * so t(k) can be taken from polling timestamp + */ + + /* encapsulate a (linear) kalman filter + * together with event publishing + */ + class KalmanFilter { + public: + using MatrixXd = Eigen::MatrixXd; + using VectorXd = Eigen::VectorXd; + using utc_nanos = xo::time::utc_nanos; + + public: + /* create filter with specification given by spec, and initial state s0 */ + explicit KalmanFilter(KalmanFilterSpec spec); + + uint32_t step_no() const { return state_ext_->step_no(); } + utc_nanos tm() const { return state_ext_->tm(); } + KalmanFilterSpec const & filter_spec() const { return filter_spec_; } + KalmanFilterStep const & step() const { return step_; } + ref::rp const & state_ext() const { return state_ext_; } + + /* notify kalman filter with input for time t(k+1) = input_kp1.tkp1() + * Require: input.tkp1() >= .current_tm() + * Promise: + * - .tm() = input_kp1.tkp1() + * - .step_no() = old .step_no() + 1 + * - .filter_spec_k, .step_k, .state_k updated + * for observations in input_kp1 + */ + void notify_input(ref::rp const & input_kp1); + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* specification for kalman filter; + * produces process/observation matrices on demand + */ + KalmanFilterSpec filter_spec_; + + /* filter step for most recent observation */ + KalmanFilterStep step_; + + /* filter state as of most recent observation; + * result of applying KalmanFilterEngine::step() to contents of .step + */ + ref::rp state_ext_; + }; /*KalmanFilter*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilter const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + + +/* end KalmanFilter.hpp */ diff --git a/KalmanFilterEngine.cpp b/KalmanFilterEngine.cpp new file mode 100644 index 00000000..e80f7497 --- /dev/null +++ b/KalmanFilterEngine.cpp @@ -0,0 +1,360 @@ +/* @file KalmanFilterEngine.cpp + * + */ + +#include "KalmanFilterEngine.hpp" +#include "print_eigen.hpp" +#include "indentlog/scope.hpp" +#include "Eigen/src/Core/Matrix.h" + +namespace xo { + using xo::time::utc_nanos; + using logutil::matrix; + using xo::scope; + using xo::xtag; + using Eigen::LDLT; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + // ----- KalmanFilterEngine ----- + + ref::rp + KalmanFilterEngine::extrapolate(utc_nanos tkp1, + ref::rp const & s, + KalmanFilterTransition const & f) + { + //constexpr char const * c_self_name + // = "KalmanFilterEngine::extrapolate"; + + /* prior estimates at t(k) */ + VectorXd const & x = s->state_v(); + MatrixXd const & P = s->state_cov(); + + /* model change from t(k) -> t(k+1) */ + MatrixXd const & F = f.transition_mat(); + MatrixXd const & Q = f.transition_cov(); + + if(F.cols() != x.rows()) { + scope log(XO_DEBUG(true /*debug_flag*/)); + + log("error: F*x: expected F.cols=x.rows", + xtag("F.cols", F.cols()), xtag("x.rows", x.rows())); + } + + /* x(k+1|k) */ + VectorXd x_ext = F * x; + + /* P(k+1|k) */ + MatrixXd P_ext = (F * P * F.transpose()) + Q; + + /* creating new state object here + * allows KalmanFilterSvc.is_volatile()=false + */ + + return KalmanFilterState::make(s->step_no() + 1, + tkp1, + std::move(x_ext), + std::move(P_ext), + f); + } /*extrapolate*/ + + VectorXd + KalmanFilterEngine::kalman_gain1(ref::rp const & skp1_ext, + KalmanFilterObservable const & h, + uint32_t j) + { + constexpr bool c_debug_enabled = false; + + scope log(XO_DEBUG(c_debug_enabled)); + + /* P(k+1|k) :: [n x n] */ + MatrixXd const & P_ext = skp1_ext->state_cov(); + + /* H(k) :: [m x n] */ + MatrixXd const & H = h.observable(); + /* R(k) :: [m x m] */ + MatrixXd const & R = h.observable_cov(); + + /* i'th col of H couples element #i of filter state to each member of input z(k); + * j'th row of H couples filter state to j'th observable + * + * Hj :: [1 x n] Hj is a row-vector + */ + auto Hj = H.row(j); + + /* Rjj is the j'th diagonal element of R */ + double Rjj = R(j, j); + + /* T + * M(k) = Hj * P(k+1|k) * Hj + Rjj + * + * M(k) is a [1 x 1] matrix + */ + double m = Hj * (P_ext * Hj.transpose()) + Rjj; + + /* -1 + * M(k) trivial, since M is [1 x 1] + */ + double m_inv = 1.0 / m; + + /* K :: [n x 1] */ + VectorXd K = P_ext * Hj.transpose() * m_inv; + + log && log("result", + xtag("P(k+1|k)", matrix(P_ext)), + xtag("R", matrix(R)), + xtag("m", m)); + + return K; + } /*kalman_gain1*/ + + MatrixXd + KalmanFilterEngine::kalman_gain(ref::rp const & skp1_ext, + KalmanFilterObservable const & h) + { + scope log(XO_DEBUG(false /*debug_enabled*/)); + + /* P(k+1|k) */ + MatrixXd const & P_ext = skp1_ext->state_cov(); + + MatrixXd const & H = h.observable(); + MatrixXd const & R = h.observable_cov(); + + uint32_t m = H.rows(); + uint32_t n = H.cols(); + + if ((P_ext.rows() != n) || (P_ext.cols() != n)) { + std::string err_msg + = tostr("kalman_gain: with dim(H) = [m x n] expect dim(P) = [n x n]", + xtag("m", m), xtag("n", n), + xtag("P.rows", P_ext.rows()), + xtag("P.cols", P_ext.cols())); + + throw std::runtime_error(err_msg); + } + + if ((R.rows() != m) || (R.cols() != m)) { + std::string err_msg + = tostr("kalman_gain: with dim(H) = [m x n] expect dim(R) = [m x m]", + xtag("m", m), xtag("n", n), + xtag("R.rows", R.rows()), xtag("R.cols", R.cols())); + + throw std::runtime_error(err_msg); + } + + /* kalman gain: + * T -1 + * K(k+1) = P(k+1|k).H(k) .M + * + * T / T \ -1 + * = P(k+1|k).H(k) .| H(k).P(k+1|k).H(k) + R(k) | + * \ / + * + * Notes: + * 1. the matrix M being inverted is symmetric, since represents covariances. + * 2. if diagonal of R(k) has no zeroes (i.e. all measurements are subject to error), + * then it must be non-negative definite + * 3. unless observation errors are perfectly correlated, M(k) + * is positive definite. + * 4. even though 3. holds, there may be a nearby non-positive-definite matrix M+dM, + * Factoring M with finite-precision arithmetic solves for M+dM instead of M; + * which may run into difficulty if M is only 'slighlty' +ve definite. + * If necessary add small diagonal correction D to M, + * sufficient to make M+D positive definite. + * This is equivalent to introducing additional + * uncorrelated observation error, so benign from a robustness perspective + * 5. In generally we usually want to avoid fully realizing a matrix inverse. + * In this case need to explicitly compute K as ingredient used to + * correct state covariance later. + * 6. However, if R is diagonal (which is in practice quite likely), + * then it's easy to decompose a suite of vector observations z(k+1) = [z1, ..zm]T + * into separate zi, with dt=0 separating them. + * Can use this to avoid computing the inverse. + * See .kalman_gain1(), .correct1() + * 7. .kalman_gain() works unaltered when H, R have been reindexed + * to exclude outliers/errors; this is true because .kalman_gain() does not + * use the observation vector z[], i.e. operates entirely in the reduced + * reindexed space. + */ + + MatrixXd M = H * P_ext * H.transpose() + R; + + /* will use to write M as: + * + * T T + * M = P .L.D.L .P + * + * where: + * P is a permutation matrix + * L is lower triangular, with unit diagonal + * D is diagonal + */ + LDLT ldlt = M.ldlt(); + + /* solve for the identity matrix to realize the inverse this way */ + MatrixXd I = MatrixXd::Identity(M.rows(), M.cols()); + + /* -1 + * M + */ + MatrixXd M_inv = ldlt.solve(I); + + /* K(k+1) */ + MatrixXd K = P_ext * H.transpose() * M_inv; + + log && log("result", + xtag("k", skp1_ext->step_no()), + xtag("P(k+1|k)", matrix(P_ext)), + xtag("H", matrix(H)), + xtag("R", matrix(R)), + xtag("M", matrix(M)), + xtag("K", matrix(K))); + + return K; + } /*kalman_gain*/ + + ref::rp + KalmanFilterEngine::correct1(ref::rp const & skp1_ext, + KalmanFilterObservable const & h, + ref::rp const & zkp1, + uint32_t j) + { + uint32_t n = skp1_ext->n_state(); + /* Kj :: [n x 1] */ + VectorXd Kj = kalman_gain1(skp1_ext, h, j); + /* H :: [m x n] */ + MatrixXd const & H = h.observable(); + VectorXd const & z = zkp1->z(); + + /* Hj :: [1 x n] the j'th row of H */ + auto const & Hj = H.row(j); + + + /* x(k+1|x) :: [n x 1] */ + VectorXd const & x_ext = skp1_ext->state_v(); + + /* P(k+1|k) :: [n x n] */ + MatrixXd const & P_ext = skp1_ext->state_cov(); + + /* innovj : difference between jth 'actual observation' + * and jth 'predicted observation' + */ + double innovj = z[j] - (Hj * x_ext); + + /* x(k+1) */ + VectorXd xkp1 = x_ext + (Kj * innovj); + + MatrixXd I = MatrixXd::Identity(n, n); + /* note: Kj [n x 1], Hj [1 x n], + * so Kj * Hj [n x n], with rank 1 + */ + MatrixXd Pkp1 = (I - (Kj * Hj)) * P_ext; + + return KalmanFilterStateExt::make(skp1_ext->step_no(), + skp1_ext->tm(), + xkp1, + Pkp1, + skp1_ext->transition(), + Kj, + j, + zkp1); + } /*correct1*/ + + ref::rp + KalmanFilterEngine::correct(ref::rp const & skp1_ext, + KalmanFilterObservable const & h, + ref::rp const & zkp1) + { + uint32_t n = skp1_ext->n_state(); + /* K :: [n x m] */ + MatrixXd K = kalman_gain(skp1_ext, h); + MatrixXd const & H = h.observable(); + /* z_orig[] is original observation vector before reindexing */ + VectorXd const & z_orig = zkp1->z(); + /* reindex z_orig, keeping only elements that appear in + */ + VectorXd z = z_orig(h.keep()); + + /* 'ext' short for 'extrapolated' */ + VectorXd const & x_ext = skp1_ext->state_v(); + MatrixXd const & P_ext = skp1_ext->state_cov(); + + /* innov: difference between 'actual observations' + * and 'predicted observations' + */ + VectorXd innov = z - (H * x_ext); + + /* x(k+1) :: [n x 1] */ + VectorXd xkp1 = x_ext + K * innov; + MatrixXd I = MatrixXd::Identity(n, n); + MatrixXd Pkp1 = (I - K * H) * P_ext; + + return KalmanFilterStateExt::make(skp1_ext->step_no(), + skp1_ext->tm(), + xkp1, + Pkp1, + skp1_ext->transition(), + K, + -1 /*j: not used*/, + zkp1); + } /*correct*/ + + ref::rp + KalmanFilterEngine::step(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1) + { + ref::rp skp1_ext + = KalmanFilterEngine::extrapolate(tkp1, sk, Fk); + + ref::rp skp1 + = KalmanFilterEngine::correct(skp1_ext, Hkp1, zkp1); + + return skp1; + } /*step*/ + + ref::rp + KalmanFilterEngine::step(KalmanFilterStep const & step_spec) + { + return step(step_spec.tkp1(), + step_spec.state(), + step_spec.model(), + step_spec.obs(), + step_spec.input()); + } /*step*/ + + ref::rp + KalmanFilterEngine::step1(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1, + uint32_t j) + { + ref::rp skp1_ext + = KalmanFilterEngine::extrapolate(tkp1, sk, Fk); + + ref::rp skp1 + = KalmanFilterEngine::correct1(skp1_ext, Hkp1, zkp1, j); + + return skp1; + } /*step1*/ + + ref::rp + KalmanFilterEngine::step1(KalmanFilterStep const & step_spec, + uint32_t j) + { + return step1(step_spec.tkp1(), + step_spec.state(), + step_spec.model(), + step_spec.obs(), + step_spec.input(), + j); + } /*step1*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterEngine.cpp */ diff --git a/KalmanFilterEngine.hpp b/KalmanFilterEngine.hpp new file mode 100644 index 00000000..b8c2f949 --- /dev/null +++ b/KalmanFilterEngine.hpp @@ -0,0 +1,185 @@ +/* @file KalmanFilterEngine.hpp */ + +#pragma once + +#include "filter/KalmanFilterStep.hpp" +#include "filter/KalmanFilterState.hpp" +#include "filter/KalmanFilterTransition.hpp" +#include "filter/KalmanFilterObservable.hpp" +#include "filter/KalmanFilterInput.hpp" + +namespace xo { + namespace kalman { + class KalmanFilterEngine { + public: + using MatrixXd = Eigen::MatrixXd; + using VectorXd = Eigen::VectorXd; + using utc_nanos = xo::time::utc_nanos; + + public: + /* evolution of system state + account for system noise, + * before incorporating effect of observations z(k+1) + * x(k) --> x(k+1|k) + * P(k) --> P(k+1|k) + * + * tkp1. time t(k+1) assoc'd with step k+1 + * sk. filter state at time tk: + * sk.k = k step # (starts from 0) + * sk.tk = t(k) time t(k) assoc'd with step #k + * sk.x = x(k) estimated state at time tk + * sk.P = P(k) quality of state estimate x(k) + * (error covariance matrix) + * Fk. state transition: + * Fk.F = F(k) state transition matrix + * Fk.Q = Q(k) covariance matrix for system noise + * + * returns propagated state estimate for t(k+1): + * retval.k = k+1 + * retval.tk = t(k+1) = tkp1 + * retval.x = x(k+1|k) + * retval.P = P(k+1|k) + */ + static ref::rp extrapolate(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk); + + /* compute kalman gain matrix for filter step t(k) -> t(k+1) + * Expensive implementation using matrix inversion + * + * T + * M(k+1) = H(k).P(k+1|k).H(k) + R(k) + * + * T -1 + * K(k+1) = P(k+1|k).H(k) .M(k+1) + * + * Require: + * - skp1_ext.n_state() = Hkp1.n_state() + * + * skp1_ext. extrapolated filter state at t(k+1) + * Hkp1. relates model state to observable variables, + * for step t(k) -> t(k+1) + */ + static MatrixXd kalman_gain(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1); + + /* compute kalman gain for a single observation z(k)[j]. + * This is useful iff the observation error matrix R is diagonal. + * For diagonal R we can present a set of observations z(k) serially + * instead of all at once, with lower time complexity + * + * Kalman Filter specifies some space with m observables. + * j identifies one of those observables, indexing from 0. + * This corresponds to row #j of H(k), and element R[j,j] of R. + * + * Effectively, we are projecting the kalman filter assoc'd with + * {skp1_ext, Hkp1} to a filter with a single observable variable z(k)[j], + * then computing the (scalar) kalman gain for this 1-variable filter + * + * The gain vector tells us for each member of filter state, + * how much to adjust our optimal estimate for that member for a unit + * amount of innovation in observable #j, i.e. for difference between + * expected and actual value for that observable. + */ + static VectorXd kalman_gain1(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1, + uint32_t j); + + /* correct extrapolated state+cov estimate; + * also computes kalman gain + * + * Require: + * - skp1_ext.n_state() = Hkp1.n_state() + * - zkp1.n_obs() == Hkp1.n_observable() + * + * skp1_ext. extrapolated kalman state + covaraince at t(k+1) + * Hkp1. relates model state to observable variables + * for step t(k) -> t(k+1) + * zkp1. observations for time t(k+1) + * + * return new filter state+cov + */ + static ref::rp correct(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1); + + /* correct extrapolated filter state for observation + * of j'th filter observable z(k+1)[j] + * + * Can use this when observation errors are uncorrelated + * (i.e. observation error matrix R is diagonal) + */ + static ref::rp correct1(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1, + uint32_t j); + + /* step filter from t(k) -> t(k+1) + * + * sk. filter state from previous step: + * x (state vector), P (state covar matrix) + * Fk. transition-related params: + * F (transition matrix), Q (system noise covar matrix) + * Hkp1. observation-related params: + * H (coupling matrix), R (error covar matrix) + * zkp1. observations z(k+1) for time t(k+1) + */ + static ref::rp step(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1); + + /* step filter from t(k) -> tk(k+1) + * same as + * .step(tkp1, sk, step_spec.model(), step_spec.obs(), zkp1); + * + * step_spec. encapsulates Fk (transition-related params) + * and Q (system noise covar matrix) + */ + static ref::rp step(KalmanFilterStep const & step_spec); + + /* step filter from t(k) -> t(k+1) + * + * sk. filter state from previous step: + * x (state vector), P (state covar matrix) + * Fk. transition-related params: + * F (transition matrix), Q (system noise covar matrix) + * Hkp1. observation-related params: + * H (coupling matrix), R (error covar matrix) + * zkp1. observations z(k+1) for time t(k+1) + * j. identifies a single filter observable -- + * step will only consume observation z(k+1)[j] + */ + static ref::rp step1(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1, + uint32_t j); + + /* step filter from t(k) -> t(k+1) + * + * same as + * .step1(step_spec.tkp1(), + * step_spec.state(), + * step_spec.model(), + * step_spec.obs(), + * step_spec.input(), + * j); + * + * step_spec. encapsulates + * x (state vector), + * P (state covar matrix), + * Fk (transition-related params), + * Q (system noise covar matrix) + * z (z(k+1), observations at time t(k+1)) + * j. identifies a single filter observable -- + * step will only consume observation z(k+1)[j] + */ + static ref::rp step1(KalmanFilterStep const & step_spec, + uint32_t j); + }; /*KalmanFilterEngine*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterEngine.hpp */ diff --git a/KalmanFilterInput.cpp b/KalmanFilterInput.cpp new file mode 100644 index 00000000..03b5330f --- /dev/null +++ b/KalmanFilterInput.cpp @@ -0,0 +1,118 @@ +/* @file KalmanFilterInput.cpp */ + +#include "KalmanFilterInput.hpp" +#include "reflect/StructReflector.hpp" +#include "Eigen/src/Core/Matrix.h" +#include "print_eigen.hpp" +#include "indentlog/scope.hpp" +#include "reflect/TaggedRcptr.hpp" + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedRcptr; + using xo::reflect::StructReflector; + using xo::scope; + using logutil::matrix; + using xo::xtag; + using Eigen::MatrixXd; + using Eigen::VectorXi; + using std::uint32_t; + + namespace kalman { + ref::rp + KalmanFilterInput::make(utc_nanos tkp1, + VectorXb const & presence, + VectorXd const & z, + VectorXd const & Rd) + { + return new KalmanFilterInput(tkp1, presence, z, Rd); + } /*make*/ + + ref::rp + KalmanFilterInput::make_present(utc_nanos tkp1, + VectorXd const & z) + { + VectorXb presence = VectorXb::Ones(z.cols()); + + return new KalmanFilterInput(tkp1, + presence, + z, + VectorXd(0) /*Rd - not using*/); + } /*make*/ + + VectorXi + KalmanFilterInput::make_kept_index() const + { + scope log(XO_DEBUG(false /*!debug_flag*/)); + + log && log(xtag("presence", matrix(presence_))); + + /* count truth values */ + uint32_t mstar = 0; + + for (uint32_t j = 0, m = this->presence_.rows(); jpresence_[j]) + ++mstar; + } + + log && log(xtag("m*", mstar)); + + VectorXi keep(mstar); + + /* 2nd pass, populate keep[] */ + + uint32_t jstar = 0; + + for (uint32_t j = 0, m = this->presence_.rows(); jpresence_[j]) { + keep[jstar] = j; + ++jstar; + } + } + + log && log("keep", matrix(keep)); + + return keep; + } /*make_kept_index*/ + + void + KalmanFilterInput::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, tkp1); + REFLECT_MEMBER(sr, presence); + REFLECT_MEMBER(sr, z); + REFLECT_MEMBER(sr, Rd); + } + } /*reflect_self*/ + + reflect::TaggedRcptr + KalmanFilterInput::self_tp() + { + return Reflect::make_rctp(this); + } /*self_tp*/ + + void + KalmanFilterInput::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterInput::display_string() const + { + std::stringstream ss; + this->display(ss); + return ss.str(); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInput.cpp */ diff --git a/KalmanFilterInput.hpp b/KalmanFilterInput.hpp new file mode 100644 index 00000000..a73a72d0 --- /dev/null +++ b/KalmanFilterInput.hpp @@ -0,0 +1,121 @@ +/* @file KalmanFilterInput.hpp */ + +#pragma once + +#include "reflect/SelfTagging.hpp" +#include "time/Time.hpp" +#include "refcnt/Refcounted.hpp" +#include +#include + +namespace xo { + /* FIXME. hack here to get member access working for reflection */ + namespace vf { class StrikesetKfinput; } + + namespace kalman { + /* represents a single kalman filter input event + * comprising: + * - time tkp1 + * - observation vector z[] + * - presence bits presence[] for z + * - observation errors Rd[] + */ + class KalmanFilterInput : public reflect::SelfTagging { + public: + using TaggedRcptr = xo::reflect::TaggedRcptr; + using utc_nanos = xo::time::utc_nanos; + using VectorXb = Eigen::Array; + using VectorXi = Eigen::VectorXi; + using VectorXd = Eigen::VectorXd; + using uint32_t = std::uint32_t; + + public: + KalmanFilterInput() = default; + + static ref::rp make(utc_nanos tkp1, + VectorXb const & presence, + VectorXd const & z, + VectorXd const & Rd); + + /* create input, with all presence bits set + not using Rd */ + static ref::rp make_present(utc_nanos tkp1, + VectorXd const & z); + + /* reflect KalmanFilterInput object representation */ + static void reflect_self(); + + /* alt name -- concession to reactor::DirectSource + * when event type is KalmanFilterInput + */ + utc_nanos tm() const { return tkp1_; } + + utc_nanos tkp1() const { return tkp1_; } + uint32_t n_obs() const { return z_.size(); } + VectorXb const & presence() const { return presence_; } + VectorXd const & z() const { return z_; } + VectorXd const & Rd() const { return Rd_; } + + /* computes reindexer keep[]: + * .presence[keep[j*]] + * is the j*'th true value in .presence + */ + VectorXi make_kept_index() const; + + virtual void display(std::ostream & os) const; + std::string display_string() const; + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override; + + protected: + KalmanFilterInput(utc_nanos tkp1, VectorXb presence, VectorXd z, VectorXd Rd) + : tkp1_(tkp1), + presence_{std::move(presence)}, + z_{std::move(z)}, + Rd_{std::move(Rd)} {} + + friend class xo::vf::StrikesetKfinput; + + private: + /* t(k+1) - asof time for observations .z */ + utc_nanos tkp1_ = xo::time::timeutil::epoch(); + /* [m x 1] presence vector. + * an observation z[j] is present iff .presence[j] is true. + * rows/columns for an absent observation are removed from filter matrices + */ + VectorXb presence_; + /* [m x 1] observation vector z(k) */ + VectorXd z_; + + /* [m x 1] observation error vector Rd(k). + * This represents a side channel for passing the diagonal of + * observation matrix R(k), when both: + * (a) error of different observations are assumed to be uncorrelated (likely) + * (b) error variance is derived from input data, e.g. because of + * some input preprocessing. + * + * It's up to KalmanFilterSpec::MkStepFn to opt-in to using this information, + * which requires agreement with any input preparation step. + * + * This variable could just as well provide observation error matrix R + * + * NOTE: perhaps-cleaner alternative would be to inherit KalmanFilterInput to + * introduce additional state, then MkStepFn can dynamic_cast + */ + VectorXd Rd_; + }; /*KalmanFilterInput*/ + + using KalmanFilterInputPtr = ref::rp; + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterInput const & x) + { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInput.hpp */ diff --git a/KalmanFilterInputCallback.hpp b/KalmanFilterInputCallback.hpp new file mode 100644 index 00000000..d9b2f213 --- /dev/null +++ b/KalmanFilterInputCallback.hpp @@ -0,0 +1,14 @@ +/* @file KalmanFilterInputCallback.hpp */ + +#pragma once + +#include "refcnt/Refcounted.hpp" +#include "filter/KalmanFilter.hpp" + +namespace xo { + namespace kalman { + using KalmanFilterInputCallback = reactor::Sink1>; + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInputCallback.hpp */ diff --git a/KalmanFilterInputSource.hpp b/KalmanFilterInputSource.hpp new file mode 100644 index 00000000..30c5cbf1 --- /dev/null +++ b/KalmanFilterInputSource.hpp @@ -0,0 +1,28 @@ +/* @file KalmanFilterInputSource.hpp */ + +#pragma once + +#include "reactor/EventSource.hpp" +#include "filter/KalmanFilterInputCallback.hpp" + +namespace xo { + namespace kalman { + /* Use: + * rp src = ...; + * rp in_cb = ...; + * + * src->add_callback(in_cb); + * ... + * // src invokes in_cb->notify_input( + * src->remove_callback(in_cb); + */ + using KalmanFilterInputSource = reactor::EventSource< + /*KalmanFilterInput,*/ + KalmanFilterInputCallback + /*&KalmanFilterInputCallback::notify_filter*/ + >; + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInputSource.hpp */ + diff --git a/KalmanFilterInputToConsole.cpp b/KalmanFilterInputToConsole.cpp new file mode 100644 index 00000000..a0a848ca --- /dev/null +++ b/KalmanFilterInputToConsole.cpp @@ -0,0 +1,25 @@ +/* @file KalmanFilterInputToConsole.cpp */ + +#include "KalmanFilterInputToConsole.hpp" +#include "indentlog/print/tag.hpp" + +namespace xo { + using xo::xtag; + + namespace kalman { + ref::rp + KalmanFilterInputToConsole::make() { + return new KalmanFilterInputToConsole(); + } /*make*/ + + void + KalmanFilterInputToConsole::display(std::ostream & os) const + { + os << ""; + } /*display*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInputToConsole.cpp */ diff --git a/KalmanFilterInputToConsole.hpp b/KalmanFilterInputToConsole.hpp new file mode 100644 index 00000000..7d48ef7e --- /dev/null +++ b/KalmanFilterInputToConsole.hpp @@ -0,0 +1,30 @@ +/* @file KalmanFilterInputToConsole.hpp */ + +#pragma once + +#include "reactor/Sink.hpp" +#include "filter/KalmanFilterInput.hpp" + +namespace xo { + namespace kalman { + class KalmanFilterInputToConsole + : public xo::reactor::SinkToConsole> + { + public: + KalmanFilterInputToConsole() = default; + + static ref::rp make(); + + virtual void display(std::ostream & os) const; + //virtual std::string display_string() const; + }; /*KalmanFilterInputToConsole*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterInputToConsole const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace option*/ +} /*namespace xo*/ + +/* end KalmanFilterInputToConsole.hpp */ diff --git a/KalmanFilterObservable.cpp b/KalmanFilterObservable.cpp new file mode 100644 index 00000000..90f31460 --- /dev/null +++ b/KalmanFilterObservable.cpp @@ -0,0 +1,71 @@ +/* @file KalmanFilterObservable.cpp */ + +#include "KalmanFilterObservable.hpp" +#include "print_eigen.hpp" +#include "indentlog/scope.hpp" + +namespace xo { + using xo::scope; + using logutil::matrix; + using xo::xtag; + + namespace kalman { + KalmanFilterObservable + KalmanFilterObservable::keep_all(MatrixXd H, + MatrixXd R) + { + VectorXi keep(H.rows()); + + for (uint32_t j=0; j"; + } /*display*/ + + std::string + KalmanFilterObservable::display_string() const + { + std::stringstream ss; + this->display(ss); + return ss.str(); + } /*display_string*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterObservable.cpp */ diff --git a/KalmanFilterObservable.hpp b/KalmanFilterObservable.hpp new file mode 100644 index 00000000..87c4f9d1 --- /dev/null +++ b/KalmanFilterObservable.hpp @@ -0,0 +1,110 @@ +/* @file KalmanFilterObservable.hpp */ + +#pragma once + +#include "time/Time.hpp" +#include +#include + +namespace xo { + namespace kalman { + class KalmanFilterObservable { + public: + using MatrixXd = Eigen::MatrixXd; + using VectorXi = Eigen::VectorXi; + + public: + KalmanFilterObservable() = default; + + /* keep maps back to canonical observations z(j). + * H, R have been re-indexed + * + * If all m observations are included, then keep will be: + * [0, .., m-1] + */ + KalmanFilterObservable(VectorXi keep, MatrixXd H, MatrixXd R) + : keep_{std::move(keep)}, H_{std::move(H)}, R_{std::move(R)} { + assert(this->check_ok()); + } /*ctor*/ + + /* build KF observable object where keeping all observations */ + static KalmanFilterObservable keep_all(MatrixXd H, MatrixXd R); + + /* build KF observable object. replace H, R with reindexed versions H', R' + * according to indexing vector keep[]. keep[] indexes members of + * observation vector z(k)[j]. Reindexed z', H', R' as follows: + * + * z'[j*] = z[keep[j*]] + * H'[j*, i] = H[keep[j*], i] + * R'[j1*, j2*] = R[keep[j1*], keep[j2*]] + */ + static KalmanFilterObservable reindex(VectorXi keep, MatrixXd H, MatrixXd R); + + uint32_t n_state() const { return H_.cols(); } + uint32_t n_observable() const { return H_.rows(); } + VectorXi const & keep() const { return keep_; } + MatrixXd const & observable() const { return H_; } + MatrixXd const & observable_cov() const { return R_; } + + bool check_ok() const { + uint32_t m = H_.rows(); + bool keep_is_mx1 = (keep_.rows() == m); + bool r_is_mxm = ((R_.cols() == m) && (R_.rows() == m)); + + bool keep_is_well_ordered = true; + + /* members of .keep are non-negative integers, + * in strictly increasing order + */ + int64_t keep_jm1 = -1; + + for (uint32_t j = 0; j < keep_.rows(); ++j) { + if (keep_[j] < 0) + keep_is_well_ordered = false; + if (keep_[j] <= keep_jm1) + keep_is_well_ordered = false; + } + + /* also would like to require: R is +ve definite */ + + return keep_is_mx1 && keep_is_well_ordered && r_is_mxm; + } /*check_ok*/ + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + + private: + /* m: #of observations that survived sanity/error checks + * + * H, R here will have been re-indexed to exclude rejected observations. + * observations z will also have been re-indexed. + * + * If an observation z[j] is excluded, then also exclude: + * - j'th row of H + * - j'th row and j'th column of R + * - j'th element of z + * + * Given re-indexed H, R, the j*'th row of H goes with z[keep[j*]] + */ + + /* [m x 1] maps back to indices in original observation vector */ + VectorXi keep_; + /* [m x n] observation matrix */ + MatrixXd H_; + /* [m x m] covariance matrix for observation noise */ + MatrixXd R_; + }; /*KalmanFilterObservable*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterObservable const & x) + { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterObservable.hpp */ + diff --git a/KalmanFilterOutputCallback.hpp b/KalmanFilterOutputCallback.hpp new file mode 100644 index 00000000..69fb8484 --- /dev/null +++ b/KalmanFilterOutputCallback.hpp @@ -0,0 +1,15 @@ +/* @file KalmanFilterOutputCallback.hpp */ + +#pragma once + +#include "reactor/Sink.hpp" +#include "filter/KalmanFilter.hpp" + +namespace xo { + namespace kalman { + /* callback for consuming kalman filter output */ + using KalmanFilterOutputCallback = reactor::Sink1>; + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterOutputCallback.hpp */ diff --git a/KalmanFilterSpec.cpp b/KalmanFilterSpec.cpp new file mode 100644 index 00000000..296aeb99 --- /dev/null +++ b/KalmanFilterSpec.cpp @@ -0,0 +1,27 @@ +/* @file KalmanFilterSpec.cpp */ + +#include "KalmanFilterSpec.hpp" +#include "indentlog/scope.hpp" + +namespace xo { + using xo::tostr; + using xo::xtag; + + namespace kalman { + void + KalmanFilterSpec::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterSpec::display_string() const + { + return tostr(*this); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterSpec.cpp */ diff --git a/KalmanFilterSpec.hpp b/KalmanFilterSpec.hpp new file mode 100644 index 00000000..56ffd783 --- /dev/null +++ b/KalmanFilterSpec.hpp @@ -0,0 +1,86 @@ +/* @file KalmanFilterSpec.hpp */ + +#pragma once + +#include "filter/KalmanFilterStep.hpp" + +namespace xo { + namespace kalman { + /* full specification for a kalman filter. + * + * For a textbook linear filter, expect a KalmanFilterStep + * instance to be independent of KalmanFilterState/KalmanFilterInput. + * + * Relaxing this requirement for two reasons: + * 1. (proper) want to allow filter with variable timing between observations, + * expecially if observations are event-driven. + * In that case it's likely that state transition matrices are a function + * of elapsed time between observations. Providing filter state sk + * allows MkStepFn to use sk.tm() + * 2. (sketchy) when observations represent market data, + * desirable to treat an observation as giving one-sided information + * about true value. For example treat a bid price as evidence + * true value is higher than that bid, but don't want to constrain + * "how much higher". Certainly no reason to think that + * bid price is normally distributed around fair value. + * Allow for hack in which we + * and modulate error distribution "as if it were normal" to assess + * a non-gaussian error distribution + */ + class KalmanFilterSpec { + public: + /* typical implementation will look something like: + * mk_step(KalmanFilterState const & sk, + * KalmanFilterInputPtr const & zkp1) + * { + * KalmanFilterTransition model = ...; + * KalmanFilterObservable obs = ...; + * + * return KalmanFilterStep(sk, model, obs, zkp1); + * } + */ + using MkStepFn = std::function const & sk, + KalmanFilterInputPtr const & zkp1)>; + + public: + explicit KalmanFilterSpec(ref::rp s0, MkStepFn mkstepfn) + : start_ext_{std::move(s0)}, + mk_step_fn_{std::move(mkstepfn)} {} + + ref::rp const & start_ext() const { return start_ext_; } + /* get step parameters (i.e. matrices F, Q, H, R) + * for step t(k) -> t(k+1). + * + * We supply t(k) state s and t(k+1) observation z(k+1): + * - to allow time stepping to be observation-driven + * - to allow for selective outlier removal + */ + KalmanFilterStep make_step(ref::rp const & sk, + ref::rp const & zkp1) { + return this->mk_step_fn_(sk, zkp1); + } /*make_step*/ + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* starting state */ + ref::rp start_ext_; + + /* creates kalman filter step object on demand; + * step object specifies inputs to 1 step in discrete + * linear kalman filter + */ + MkStepFn mk_step_fn_; + }; /*KalmanFilterSpec*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterSpec const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterSpec.hpp */ diff --git a/KalmanFilterState.cpp b/KalmanFilterState.cpp new file mode 100644 index 00000000..7656908c --- /dev/null +++ b/KalmanFilterState.cpp @@ -0,0 +1,231 @@ +/* @file KalmanFilterState.cpp */ + +#include "KalmanFilterState.hpp" +#include "print_eigen.hpp" +#include "reflect/StructReflector.hpp" +#include "reflect/TaggedPtr.hpp" +#include "indentlog/scope.hpp" +#include "Eigen/src/Core/Matrix.h" +#include +#include + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedRcptr; + using xo::reflect::StructReflector; + using xo::time::utc_nanos; + using xo::ref::rp; + using logutil::matrix; + using logutil::vector; + //using xo::scope; + using xo::xtag; + using xo::tostr; + //using Eigen::LDLT; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + // ----- KalmanFilterState ----- + + rp + KalmanFilterState::make() + { + return new KalmanFilterState(); + } /*make*/ + + rp + KalmanFilterState::make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition) + { + return new KalmanFilterState(k, tk, + std::move(x), + std::move(P), + std::move(transition)); + } /*make*/ + + void + KalmanFilterState::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, k); + REFLECT_MEMBER(sr, tk); + REFLECT_MEMBER(sr, x); + REFLECT_MEMBER(sr, P); + } + } /*reflect_self*/ + + KalmanFilterState::KalmanFilterState() = default; + + KalmanFilterState::KalmanFilterState(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition) + : k_{k}, tk_{tk}, + x_{std::move(x)}, P_{std::move(P)}, + transition_{std::move(transition)} + {} + + TaggedRcptr + KalmanFilterState::self_tp() + { + return Reflect::make_rctp(this); + } /*self_tp*/ + + // ----- KalmanFilterExt ----- + + rp + KalmanFilterStateExt::make() + { + return new KalmanFilterStateExt(); + } /*make*/ + + rp + KalmanFilterStateExt::make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + rp zk) + { + return new KalmanFilterStateExt(k, + tk, + std::move(x), + std::move(P), + std::move(transition), + std::move(K), + j, + std::move(zk)); + } /*make*/ + + void + KalmanFilterStateExt::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + /* TODO: use sr.adopt_ancestors() */ + + REFLECT_EXPLICIT_MEMBER(sr, "k", &KalmanFilterState::k_); + REFLECT_EXPLICIT_MEMBER(sr, "tk", &KalmanFilterState::tk_); + REFLECT_EXPLICIT_MEMBER(sr, "x", &KalmanFilterState::x_); + REFLECT_EXPLICIT_MEMBER(sr, "P", &KalmanFilterState::P_); + REFLECT_EXPLICIT_MEMBER(sr, "transition", &KalmanFilterState::transition_); + REFLECT_MEMBER(sr, j); + REFLECT_MEMBER(sr, K); + REFLECT_MEMBER(sr, zk); + } + } /*reflect_self*/ + + KalmanFilterStateExt::KalmanFilterStateExt(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + rp zk) + : KalmanFilterState(k, tk, + std::move(x), + std::move(P), + std::move(transition)), + j_{j}, + K_{std::move(K)}, + zk_{std::move(zk)} + { + uint32_t n = x.size(); + + if (n != P.rows() || n != P.cols()) { + std::string err_msg + = tostr("with n=x.size expect [n x n] covar matrix P", + xtag("n", x.size()), + xtag("P.rows", P.rows()), + xtag("P.cols", P.cols())); + + throw std::runtime_error(err_msg); + } + + if ((K.rows() > 0) && (K.rows() > 0)) { + if (n != K.rows()) { + std::string err_msg + = tostr("with n=x.size expect [m x n] gain matrix K", + xtag("n", x.size()), + xtag("K.rows", K.rows()), + xtag("K.cols", K.cols())); + + throw std::runtime_error(err_msg); + } + } else { + /* bypass test with [0 x 0] matrix K; + * normal for initial filter state + */ + } + } /*ctor*/ + + void + KalmanFilterState::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterState::display_string() const + { + std::stringstream ss; + ss << *this; + return ss.str(); + } /*display_string*/ + + // ----- KalmanFilterStateExt ----- + + ref::rp + KalmanFilterStateExt::initial(utc_nanos t0, + VectorXd x0, + MatrixXd P0) + { + return KalmanFilterStateExt::make + (0 /*k*/, + t0, + std::move(x0), + std::move(P0), + KalmanFilterTransition(MatrixXd() /*F - not used for initial step*/, + MatrixXd() /*Q - not used for initial step*/), + MatrixXd() /*K - not used for initial step*/, + -1 /*j - not used for initial step*/, + nullptr /*zk - not defined for initial step*/); + } /*initial*/ + + void + KalmanFilterStateExt::display(std::ostream & os) const + { + os << "step_no()) + << xtag("tk", this->tm()) + << xtag("x", matrix(this->state_v())) + << xtag("P", matrix(this->state_cov())) + << xtag("K", matrix(K_)) + << xtag("j", j_) + << ">"; + } /*display*/ + + TaggedRcptr + KalmanFilterStateExt::self_tp() + { + return Reflect::make_rctp(this); + } /*self_tp*/ + } /*namespace filter*/ +} /*namespace xo*/ + +/* end KalmanFilterState.cpp */ diff --git a/KalmanFilterState.hpp b/KalmanFilterState.hpp new file mode 100644 index 00000000..113c4c6b --- /dev/null +++ b/KalmanFilterState.hpp @@ -0,0 +1,163 @@ +/* @file KalmanFilterState.hpp */ + +#pragma once + +#include "reflect/SelfTagging.hpp" +#include "filter/KalmanFilterInput.hpp" +#include "filter/KalmanFilterTransition.hpp" +#include "time/Time.hpp" +#include +#include +#include + +namespace xo { + namespace kalman { + /* encapsulate state (i.e. initial state, and output after each step) + * for a kalman filter + */ + class KalmanFilterState : public reflect::SelfTagging { + public: + using TaggedRcptr = reflect::TaggedRcptr; + using utc_nanos = xo::time::utc_nanos; + using VectorXd = Eigen::VectorXd; + using MatrixXd = Eigen::MatrixXd; + using uint32_t = std::uint32_t; + + public: + static ref::rp make(); + static ref::rp make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition); + virtual ~KalmanFilterState() = default; + + /* reflect KalmanFilterState object representation */ + static void reflect_self(); + + uint32_t step_no() const { return k_; } + utc_nanos tm() const { return tk_; } + /* with n = .n_state(): + * x_ is [n x 1] vector + * P_ is [n x n] matrix, + */ + uint32_t n_state() const { return x_.size(); } + VectorXd const & state_v() const { return x_; } + MatrixXd const & state_cov() const { return P_; } + + KalmanFilterTransition const & transition() const { return transition_; } + + virtual void display(std::ostream & os) const; + std::string display_string() const; + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override; + + private: + KalmanFilterState(); + KalmanFilterState(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition); + + friend class KalmanFilterStateExt; + + private: + /* step# k, advances by +1 on each filter step */ + uint32_t k_ = 0; + /* time t(k) */ + utc_nanos tk_; + /* [n x 1] (estimated) system state xk = x(k) */ + VectorXd x_; + /* [n x n] covariance matrix for error assoc'd with with x(k) + * P(i,j) is the covariance of (ek[i], ek[j]), + * where ex(k) is the difference (x(k) - x_(k)) + * between estimated state x(k) + * (= this->x_) and model state x_(k) + */ + MatrixXd P_; + + /* F, Q matrices driving .x, .P */ + KalmanFilterTransition transition_; + }; /*KalmanFilterState*/ + + inline std::ostream & operator<<(std::ostream & os, + KalmanFilterState const & s) + { + s.display(os); + return os; + } /*operator<<*/ + + /* KalmanFilterStateExt: + * adds additional details from filter step to KalmanFilterState + */ + class KalmanFilterStateExt : public KalmanFilterState { + public: + using TaggedRcptr = reflect::TaggedRcptr; + using MatrixXd = Eigen::MatrixXd; + using int32_t = std::int32_t; + + public: + static ref::rp make(); + static ref::rp make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + ref::rp zk); + + /* create state object for initial filter state */ + static ref::rp initial(utc_nanos t0, + VectorXd x0, + MatrixXd P0); + + /* reflect KalmanFilterStateExt object representation */ + static void reflect_self(); + + int32_t observable() const { return j_; } + MatrixXd const & gain() const { return K_; } + ref::rp const & zk() const { return zk_; } + + virtual void display(std::ostream & os) const override; + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override; + + private: + KalmanFilterStateExt() = default; + KalmanFilterStateExt(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + ref::rp zk); + + private: + /* if -1: not used; + * if >= 0: identifies j'th of m observables; + * gain .K applies just to information obtainable from + * observing that scalar variable + */ + int32_t j_ = -1; + /* if .j is -1: + * [n x n] kalman gain + * if .j >= 0: + * [n x 1] kalman gain for observable #j + */ + MatrixXd K_; + /* input leading to state k. + * empty for initial state (i.e. when .k is 0) + */ + ref::rp zk_; + }; /*KalamnFilterStateExt*/ + } /*namespace filter*/ +} /*namespace xo*/ + +/* end KalmanFilterState.hpp */ diff --git a/KalmanFilterStateToConsole.cpp b/KalmanFilterStateToConsole.cpp new file mode 100644 index 00000000..8e76ce84 --- /dev/null +++ b/KalmanFilterStateToConsole.cpp @@ -0,0 +1,25 @@ +/* @file KalmanFilterStateToConsole.cpp */ + +#include "KalmanFilterStateToConsole.hpp" +#include "indentlog/print/tag.hpp" + +namespace xo { + using xo::xtag; + + namespace kalman { + ref::rp + KalmanFilterStateToConsole::make() { + return new KalmanFilterStateToConsole(); + } /*make*/ + + void + KalmanFilterStateToConsole::display(std::ostream & os) const + { + os << ""; + } /*display*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterStateToConsole.cpp */ diff --git a/KalmanFilterStateToConsole.hpp b/KalmanFilterStateToConsole.hpp new file mode 100644 index 00000000..0d525cea --- /dev/null +++ b/KalmanFilterStateToConsole.hpp @@ -0,0 +1,30 @@ +/* @file KalmanFilterStateToConsole.hpp */ + +#pragma once + +#include "reactor/Sink.hpp" +#include "filter/KalmanFilterState.hpp" + +namespace xo { + namespace kalman { + class KalmanFilterStateToConsole + : public xo::reactor::SinkToConsole + { + public: + KalmanFilterStateToConsole() = default; + + static ref::rp make(); + + virtual void display(std::ostream & os) const; + //virtual std::string display_string() const; + }; /*KalmanFilterStateToConsole*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterStateToConsole const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace option*/ +} /*namespace xo*/ + +/* end KalmanFilterStateToConsole.hpp */ diff --git a/KalmanFilterStep.cpp b/KalmanFilterStep.cpp new file mode 100644 index 00000000..f62f2d21 --- /dev/null +++ b/KalmanFilterStep.cpp @@ -0,0 +1,84 @@ +/* @file KalmanFilterStep.cpp */ + +#include "KalmanFilterStep.hpp" +#include "filter/KalmanFilterEngine.hpp" +#include "filter/KalmanFilterState.hpp" +#include "indentlog/scope.hpp" + +namespace xo { + using xo::scope; + using xo::tostr; + using xo::xtag; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + ref::rp + KalmanFilterStep::extrapolate() const + { + return KalmanFilterEngine::extrapolate(this->tkp1(), + this->state(), + this->model() /*transition*/); + } /*extrapolate*/ + + MatrixXd + KalmanFilterStep::gain(ref::rp const & skp1_ext) const + { + return KalmanFilterEngine::kalman_gain(skp1_ext, + this->obs()); + } /*gain*/ + + VectorXd + KalmanFilterStep::gain1(ref::rp const & skp1_ext, + uint32_t j) const + { + return KalmanFilterEngine::kalman_gain1(skp1_ext, + this->obs(), + j); + + } /*gain1*/ + + ref::rp + KalmanFilterStep::correct(ref::rp const & skp1_ext) + { + return KalmanFilterEngine::correct(skp1_ext, + this->obs(), + this->input()); + } /*correct*/ + + ref::rp + KalmanFilterStep::correct1(ref::rp const & skp1_ext, + uint32_t j) + { + return KalmanFilterEngine::correct1(skp1_ext, + this->obs(), + this->input(), + j); + } /*correct1*/ + + void + KalmanFilterStep::display(std::ostream & os) const + { + //scope lscope("KalmanFilterStep::display"); + + os << "model()); + //lscope.log("obs:"); + os << xtag("obs", this->obs()); + //lscope.log("input:"); + os << xtag("input", this->input()); + os << ">"; + } /*display*/ + + std::string + KalmanFilterStep::display_string() const + { + return tostr(*this); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterStep.cpp */ diff --git a/KalmanFilterStep.hpp b/KalmanFilterStep.hpp new file mode 100644 index 00000000..89813cc2 --- /dev/null +++ b/KalmanFilterStep.hpp @@ -0,0 +1,128 @@ +/* @file KalmanFilterStep.hpp */ + +#pragma once + +#include "KalmanFilterState.hpp" +#include "KalmanFilterInput.hpp" +#include "KalmanFilterTransition.hpp" +#include "KalmanFilterObservable.hpp" + +namespace xo { + namespace kalman { + /* encapsulate {state + observation} models for a single time step t(k). + * Emitted by KalmanFilterSpec, q.v. + */ + class KalmanFilterStepBase { + public: + KalmanFilterStepBase() = default; + KalmanFilterStepBase(KalmanFilterTransition model, + KalmanFilterObservable obs) + : model_{std::move(model)}, + obs_{std::move(obs)} {} + + /* aka system_model() */ + KalmanFilterTransition const & model() const { return model_; } + KalmanFilterObservable const & obs() const { return obs_; } + + private: + /* model for process being observed (state transition + noise) */ + KalmanFilterTransition model_; + /* what can be observed (observables + noise) */ + KalmanFilterObservable obs_; + }; /*KalmanFilterStepBase*/ + + /* encapsulate {state + observation} models for a single time step t(k). + * Emitted by KalmanFilterSpec, q.v. + * + * holds: + * x(k) + * P(k) + * F(k) + * H(k+1) + * z(k+1) + * + * contains all the inputs needed to compute: + * x(k+1) + * P(k+1) + * + * does not provide that result + */ + class KalmanFilterStep : public KalmanFilterStepBase { + public: + using utc_nanos = xo::time::utc_nanos; + using MatrixXd = Eigen::MatrixXd; + using VectorXd = Eigen::VectorXd; + + public: + KalmanFilterStep() = default; + KalmanFilterStep(ref::rp state, + KalmanFilterTransition model, + KalmanFilterObservable obs, + ref::rp zkp1) + : KalmanFilterStepBase(model, obs), + state_{std::move(state)}, + input_{std::move(zkp1)} {} + + ref::rp const & state() const { return state_; } + ref::rp const & input() const { return input_; } + + utc_nanos tkp1() const { return input_->tkp1(); } + + /* extrapolate kalman filter state forward to time + * .tkp1() (i.e. to t(k+1)); computes + * x(k+1|k) + * P(k+1|k) + * does not use the t(k+1) observations .input.z + */ + ref::rp extrapolate() const; + + /* compute kalman gain matrix K(k+1) + * given extrapolated t(k+1) state skp1_ext = {x(k+1|k), P(k+1|k)} + * + * note that .state() != skp1_ext; .state() reports {x(k), P(k)} + */ + MatrixXd gain(ref::rp const & skp1_ext) const; + + /* compute kalman gain vector K(k+1) + * given extrapolated t(k+1) state skp1_ext = {x(k+1|k), P(k+1|k)}, + * on behalf of a single observation z[j]. + * actual observation z[j] is not given here, + * just computing the gain vector. i'th member of gain vector + * gives effect of innovation on i'th member of kalman filter state. + */ + VectorXd gain1(ref::rp const & skp1_ext, + uint32_t j) const; + + /* compute correction to extrapolated filter state {x(k+1|k), P(k+1|k)}, + * for observation z(k+1) = .input.z() + */ + ref::rp correct(ref::rp const & skp1_ext); + + /* compute correction to extrapolated filter state skp1_ext = {x(k+1|k), P(k+1|k)}, + * for a single observation z(k+1, j) = .input.z()[j] + */ + ref::rp correct1(ref::rp const & skp1_ext, + uint32_t j); + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* system state: timestamp, estimated process state, process covariance + * asof beginning of this step + */ + ref::rp state_; + /* input: observations at time t(k+1) */ + KalmanFilterInputPtr input_; + }; /*KalmanFilterStep*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterStep const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterStep.hpp */ diff --git a/KalmanFilterSvc.cpp b/KalmanFilterSvc.cpp new file mode 100644 index 00000000..5e32b102 --- /dev/null +++ b/KalmanFilterSvc.cpp @@ -0,0 +1,44 @@ +/* @file KalmanFilterSvc.cpp */ + +#include "KalmanFilterSvc.hpp" + +namespace xo { + using xo::ref::rp; + using xo::scope; + using xo::xtag; + + namespace kalman { + rp + KalmanFilterSvc::make(KalmanFilterSpec spec) + { + return new KalmanFilterSvc(std::move(spec)); + } /*make*/ + + KalmanFilterSvc::KalmanFilterSvc(KalmanFilterSpec spec) + : filter_{std::move(spec)} + {} + + void + KalmanFilterSvc::notify_ev(ref::rp const & input_kp1) + { + this->filter_.notify_input(input_kp1); + + ++(this->n_in_ev_); + this->notify_secondary_event(this->filter_.state_ext()); + } /*notify_input*/ + + void + KalmanFilterSvc::display(std::ostream & os) const + { + os << "name()) + << xtag("n_in_ev", this->n_in_ev()) + << xtag("n_queued_out_ev", this->n_queued_out_ev()) + << xtag("n_out_ev", this->n_out_ev()) + //<< xtag("filter", this->filter_) + << ">"; + } /*display*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterSvc.cpp */ diff --git a/KalmanFilterSvc.hpp b/KalmanFilterSvc.hpp new file mode 100644 index 00000000..33d25514 --- /dev/null +++ b/KalmanFilterSvc.hpp @@ -0,0 +1,64 @@ +/* @file KalmanFilterSvc.hpp */ + +#include "reactor/Sink.hpp" +#include "reactor/DirectSourcePtr.hpp" +#include "filter/KalmanFilter.hpp" +#include "filter/KalmanFilterInputSource.hpp" +#include "filter/KalmanFilterOutputCallback.hpp" +#include "callback/CallbackSet.hpp" + +namespace xo { + namespace kalman { + /* encapsulate a passive KalmanFilter + * instance as an active event consumer+producer + * + * sinks that want to consume KalmanFilterSvc events will use + * .attach_sink() (or .add_callback()) + */ + class KalmanFilterSvc : public xo::reactor::Sink1>, + public xo::reactor::DirectSourcePtr> { + public: + using AbstractSource = xo::reactor::AbstractSource; + + public: + /* named ctor idiom */ + static ref::rp make(KalmanFilterSpec spec); + + KalmanFilter const & filter() const { return filter_; } + + /* notify incoming observations; will trigger kalman filter step */ + void notify_ev(ref::rp const & input_kp1) override; + + // ----- inherited from reactor::AbstractSink ----- + + /* filter captures KF input pointer */ + virtual bool allow_volatile_source() const override { return false; } + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + virtual void display(std::ostream & os) const override; + + // ----- inherited from reactor::AbstractSource ----- + + /* note: correct since KalmanFilterEngine.extrapolate() + * always creates new state object + */ + virtual bool is_volatile() const override { return false; } + + // ----- Inherited from AbstractEventProcessor ----- + + private: + KalmanFilterSvc(KalmanFilterSpec spec); + + private: + /* passive kalman filter */ + KalmanFilter filter_; + /* receive filter input from this source; see .attach_input() */ + ref::rp input_src_; + /* counts lifetime #of input events (see .notify_ev()) */ + uint32_t n_in_ev_ = 0; + /* publish filter state updates to these callbacks */ + fn::RpCallbackSet pub_; + }; /*KalmanFilterSvc*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* KalmanFilterSvc.hpp */ diff --git a/KalmanFilterTransition.cpp b/KalmanFilterTransition.cpp new file mode 100644 index 00000000..65be627b --- /dev/null +++ b/KalmanFilterTransition.cpp @@ -0,0 +1,55 @@ +/* @file KalmanFilterTransition.cpp */ + +#include "KalmanFilterTransition.hpp" +#include "reflect/StructReflector.hpp" +#include "print_eigen.hpp" +#include "indentlog/scope.hpp" + +namespace xo { + using xo::reflect::StructReflector; + using logutil::matrix; + using xo::xtag; + + namespace kalman { + void + KalmanFilterTransition::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, F); + REFLECT_MEMBER(sr, Q); + } + } /*reflect_self*/ + + uint32_t + KalmanFilterTransition::n_state() const + { + /* we know F.rows() == F.cols() = Q.cols() == Q.rows(), + * see .check_ok() + */ + + return F_.rows(); + } /*n_state*/ + + void + KalmanFilterTransition::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterTransition::display_string() const + { + std::stringstream ss; + this->display(ss); + return ss.str(); + } /*display_string*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterTransition.cpp */ diff --git a/KalmanFilterTransition.hpp b/KalmanFilterTransition.hpp new file mode 100644 index 00000000..fc777c00 --- /dev/null +++ b/KalmanFilterTransition.hpp @@ -0,0 +1,62 @@ +/* @file KalmanFilterTransition.hpp */ + +#pragma once + +#include "time/Time.hpp" +#include +#include + +namespace xo { + namespace kalman { + + /* encapsulate transition behavior for a kalman filter + * before taking observations into account + */ + class KalmanFilterTransition { + public: + using MatrixXd = Eigen::MatrixXd; + using uint32_t = std::uint32_t; + + public: + KalmanFilterTransition() = default; + KalmanFilterTransition(MatrixXd F, + MatrixXd Q) + : F_{std::move(F)}, Q_{std::move(Q)} { assert(this->check_ok()); } + + static void reflect_self(); + + /* n: cardinality of state vector */ + uint32_t n_state() const; + + MatrixXd const & transition_mat() const { return F_; } + MatrixXd const & transition_cov() const { return Q_; } + + bool check_ok() const { + uint32_t n = F_.rows(); + bool f_is_nxn = ((F_.rows() == n) && (F_.cols() == n)); + bool q_is_nxn = ((Q_.rows() == n) && (Q_.cols() == n)); + + /* also would like to require: Q is +ve definite */ + + return f_is_nxn && q_is_nxn; + } /*check_ok*/ + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* [n x n] state transition matrix */ + MatrixXd F_; + /* [n x n] covariance matrix for system noise */ + MatrixXd Q_; + }; /*KalmanFilterTransition*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterTransition const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterTransition.hpp */ diff --git a/init_filter.cpp b/init_filter.cpp new file mode 100644 index 00000000..80407cc4 --- /dev/null +++ b/init_filter.cpp @@ -0,0 +1,51 @@ +/* file init_filter.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "init_filter.hpp" +#include "reactor/init_reactor.hpp" +#include "KalmanFilterState.hpp" +#include "EigenUtil.hpp" +#include "printjson/PrintJson.hpp" + +namespace xo { + using xo::kalman::KalmanFilterInput; + using xo::kalman::KalmanFilterTransition; + using xo::kalman::KalmanFilterState; + using xo::kalman::KalmanFilterStateExt; + using xo::eigen::EigenUtil; + using xo::json::PrintJsonSingleton; + using xo::json::PrintJson; + + void + InitSubsys::init() + { + PrintJson * pjson = PrintJsonSingleton::instance().get(); + + EigenUtil::reflect_eigen(); + EigenUtil::provide_json_printers(pjson); + + KalmanFilterInput::reflect_self(); + KalmanFilterTransition::reflect_self(); + KalmanFilterState::reflect_self(); + KalmanFilterStateExt::reflect_self(); + + } /*init*/ + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* subsystem dependencies for filter/ */ + retval ^= InitSubsys::require(); + + /* filter/'s own initialization code */ + retval ^= Subsystem::provide("filter", &init); + + return retval; + } /*require*/ +} /*namespace xo*/ + +/* end init_filter.cpp */ diff --git a/init_filter.hpp b/init_filter.hpp new file mode 100644 index 00000000..79cfc6d0 --- /dev/null +++ b/init_filter.hpp @@ -0,0 +1,20 @@ +/* file init_filter.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "subsys/Subsystem.hpp" + +namespace xo { + enum S_filter_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + +/* end init_filter.hpp */ diff --git a/print_eigen.hpp b/print_eigen.hpp new file mode 100644 index 00000000..953c5904 --- /dev/null +++ b/print_eigen.hpp @@ -0,0 +1,41 @@ +/* @file print_eigen.hpp */ + +#include +#include + +namespace logutil { + template + class matrix { + public: + matrix(T x) : x_{std::move(x)} {} + + /* print this value */ + T x_; + }; /*matrix*/ + + template + using vector = matrix; + + template + inline std::ostream & + operator<<(std::ostream & s, matrix const & mat) + { + s << "["; + for(std::uint32_t i = 0, m = mat.x_.rows(); i 0) + s << "; "; + + for(std::uint32_t j = 0, n = mat.x_.cols(); j 0) + s << ' '; + + s << mat.x_(i, j); + } + } + s << "]"; + + return s; + } /*operator<<*/ +} /*namespace logutil*/ + +/* end print_eigen.hpp */ From 49d2fd37572db5dfb844ad160b8989bac71ef5e7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 11:10:55 -0400 Subject: [PATCH 0384/2524] initial implementation --- CMakeLists.txt | 64 +++++++ cmake/xo_statisticsConfig.cmake.in | 4 + include/statistics/Accumulator.hpp | 10 ++ include/statistics/Histogram.hpp | 217 ++++++++++++++++++++++++ include/statistics/SampleStatistics.hpp | 130 ++++++++++++++ 5 files changed, 425 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/xo_statisticsConfig.cmake.in create mode 100644 include/statistics/Accumulator.hpp create mode 100644 include/statistics/Histogram.hpp create mode 100644 include/statistics/SampleStatistics.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..68eb1445 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,64 @@ +# xo-statistics/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_statistics VERSION 1.0) +enable_language(CXX) + +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# 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. +# +add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) + +# ---------------------------------------------------------------- +# bespoke (usually temporary) c++ settings + +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- +# common include paths etc. + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# external dependencies +# +# set CMAKE_INSTALL_PREFIX to analog of /usr +# to use .cmake assistants from /usr/lib/cmake/indentlog +# +# xo_dependency(..) + +# ---------------------------------------------------------------- + +#add_subdirectory(example) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# output targets + +set(SELF_LIB xo_statistics) +xo_add_headeronly_library(${SELF_LIB}) + +# ---------------------------------------------------------------- +# standard install + provide find_package() support + +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# install additional components + +#install(TARGETS statistics_ex1 DESTINATION bin/xo-statistics/example) diff --git a/cmake/xo_statisticsConfig.cmake.in b/cmake/xo_statisticsConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_statisticsConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/statistics/Accumulator.hpp b/include/statistics/Accumulator.hpp new file mode 100644 index 00000000..69763e38 --- /dev/null +++ b/include/statistics/Accumulator.hpp @@ -0,0 +1,10 @@ +/* @file Accumulator.hpp */ + +namespace xo { + nmaespace statistics { + class Accumulator { + }; /*Accumulator*/ + } /*namespace statistics*/ +} /*namespace xo*/ + +/* end Accumulator.hpp */ diff --git a/include/statistics/Histogram.hpp b/include/statistics/Histogram.hpp new file mode 100644 index 00000000..d2219f23 --- /dev/null +++ b/include/statistics/Histogram.hpp @@ -0,0 +1,217 @@ +/* @file Histogram.hpp */ + +#pragma once + +#include "statistics/SampleStatistics.hpp" +#include "logutil/scope.hpp" +#include +#include +#include + +namespace xo { + namespace statistics { + /* sample statistics for a histogram bucket + * (editorial: compare with distribution::Counter) + */ + class Bucket { + public: + Bucket() = default; + Bucket(uint32_t n_sample, double sum, double mean, double mom2) + : n_sample_(n_sample), sum_(sum), mean_(mean), moment2_(mom2) {} + + uint32_t n_sample() const { return n_sample_; } + double sum() const { return sum_; } + double mean() const { return mean_; } + double sample_variance() const { return (n_sample_ > 1) ? moment2_ / (n_sample_ - 1) : 0.0; } + double standard_error() const { return ::sqrt(this->sample_variance()); } + + /* to estimate standard error of the mean: + * 0. let nk = .n_sample be the #of samples falling into this bin. + * n is the total #of samples across all bins. + * (i.e. Histogram.n_sample) + * 1. imagine probability of a sample falling in this bin + * is the observed frequency p = (.n_sample / n) + * 2. imagine a Bernoulli random variable Bp(i) associated with each sample x(i) + * {1, with probability p; 0 with probability q=1-p}) + * 3. each Bp(i) has mean p, variance p(1-p) + * 4. sum of the Bp(1) .. Bp(n) has mean n.p = nk, + * variance + * n.p.(1-p) + * = n.(nk/n).(1 - nk/n) + * = nk.(1 - nk/n) + * (by central limit theorem we can treat this as approximately normal + * for sufficiently large n) + * 5. standard error of Sum{Bp(i)} + * will be + * sqrt(nk.(1 - nk/n)) + */ + double n_sample_stderr(uint32_t n) const { + double nr = 1.0 / n; + uint32_t nk = this->n_sample_; + + return ::sqrt(nk * (1.0 - nk * nr)); + } /*n_sample_stderr*/ + + /* add one sample, x, to this bucket */ + void include_sample(double x) { + using logutil::scope; + using logutil::xtag; + + constexpr char const * c_self = "Bucket::include_sample"; + constexpr bool c_logging_enabled = false; + + /* size of sample _before_ adding x */ + int n = this->n_sample_; + + this->n_sample_ = n+1; + this->sum_ += x; + + double mean_n = this->mean_; + double mom2_n = this->moment2_; + double mean_np1 = SampleStatistics::update_online_mean(x, n, mean_n); + double mom2_np1 = SampleStatistics::update_online_moment2(x, + mean_np1, mean_n, + mom2_n); + scope lscope(c_self, c_logging_enabled); + if(c_logging_enabled) { + lscope.log("update", + xtag("x", x), xtag("n", n), + xtag("sum", sum_), + xtag("mean(n)", mean_n), + xtag("mom2(n)", mom2_n), + xtag("mean(n+1)", mean_np1), + xtag("mom2(n+1)", mom2_np1)); + } + + this->mean_ = mean_np1; + this->moment2_ = mom2_np1; + } /*include_sample*/ + + private: + /* #of samples in this bucket (will be #of times .sample() has been called) */ + uint32_t n_sample_ = 0; + /* sum of samples in this bucket */ + double sum_ = 0.0; + /* mean of values in this bucket + * -- use online algo to avoid catastrophic errors for large #samples + */ + double mean_ = 0.0; + double moment2_ = 0.0; + }; /*Bucket*/ + + /* accumulate histogram on sampled data */ + class Histogram { + public: + using const_iterator = std::vector::const_iterator; + + public: + Histogram(uint32_t n_interior_bucket, double lo_bucket, double hi_bucket) + : n_interior_bucket_(n_interior_bucket), + lo_bucket_(lo_bucket), + hi_bucket_(hi_bucket), + bucket_v_(n_interior_bucket + 2) + {} + + uint32_t n_sample() const { return n_sample_; } + uint32_t n_bucket() const { return n_interior_bucket_ + 2; } + + double bucket_width() const { return (this->hi_bucket_ - this->lo_bucket_) / this->n_interior_bucket_; } + + const_iterator begin() const { return bucket_v_.begin(); } + const_iterator end() const { return bucket_v_.end(); } + Bucket const & lookup(uint32_t ix) const { return this->bucket_v_[ix]; } + + /* compute bucket representing pooled sample combining + * contents of buckets [lo .. hi) + */ + Bucket pooled(uint32_t lo, uint32_t hi) const { + /* NOTE: for pooled bucket, may want to compute "reliability variance", + * i.e. report + * M2 / (N - (sum(nk^2) / N)) + * instead of + * M2 / (N - 1) + */ + + uint32_t n_sample = 0; + double sum = 0.0; + double mean = 0.0; + double mom2 = 0.0; + + for(uint32_t i = lo; ilookup(i); + + n_sample += bucket.n_sample(); + /* note that sum is not numerically well-behaved if summing + * over a large #of buckets + */ + sum += bucket.sum(); + + double prev_mean = mean; + /* relative weight of bucket b(i) relative to pooled statistics + * from buckets b(lo) .. b(i-1) + */ + double wt = (bucket.n_sample() / static_cast(n_sample)); + + /* similar to SampleStatistics::update_online_mean() */ + mean = prev_mean + wt * (bucket.mean() - prev_mean); + /* similar to SampleStatistics::update_online_moment2() */ + mom2 = (mom2 + (bucket.n_sample() + * (bucket.mean() - prev_mean) + * (bucket.mean() - mean))); + } + + return Bucket(n_sample, sum, mean, mom2); + } /*pooled*/ + + double bucket_lo_edge(uint32_t ix) const { + if(ix == 0) { + return -std::numeric_limits::infinity(); + } else { + return this->lo_bucket_ + (ix - 1) * this->bucket_width(); + } + } /*bucket_lo_edge*/ + + double bucket_hi_edge(uint32_t ix) const { + if(ix < n_interior_bucket_ + 1) + return this->lo_bucket_ + ix * this->bucket_width(); + else + return std::numeric_limits::infinity(); + } /*bucket_hi_edge*/ + + /* index (into .bucket_v[]) of bucket to use for a sample with value x */ + uint32_t bucket_ix(double x) const { + if(x < this->lo_bucket_) + return 0; + + if(x < this->hi_bucket_) + return 1 + static_cast((x - this->lo_bucket_) / this->bucket_width()); + + return this->n_interior_bucket_ + 1; + } /*bucket_ix*/ + + void include_sample(double x) { + uint32_t ix = this->bucket_ix(x); + + ++(this->n_sample_); + this->bucket_v_[ix].include_sample(x); + } /*include_sample*/ + + private: + /* #of samples across all buckets */ + uint32_t n_sample_ = 0; + /* #of interior buckets: split [.lo_bucket, .hi_bucket] into + * equally-spaced intervals of width (.hi_bucket - .lo_bucket) / .n_bucket + */ + uint32_t n_interior_bucket_ = 0; + /* right edge of first bucket (left edge is -oo) */ + double lo_bucket_ = 0.0; + /* left edge of last bucket (right edge is +oo) */ + double hi_bucket_ = 0.0; + + /* hisogram buckets */ + std::vector bucket_v_; + }; /*Histogram*/ + } /*namespace statistics*/ +} /*namespace xo*/ + +/* end Histogram.hpp */ diff --git a/include/statistics/SampleStatistics.hpp b/include/statistics/SampleStatistics.hpp new file mode 100644 index 00000000..a7d595b0 --- /dev/null +++ b/include/statistics/SampleStatistics.hpp @@ -0,0 +1,130 @@ +/* @file SampleStatistics.hpp */ + +#pragma once + +#include + +namespace xo { + namespace statistics { + /* accumlate statistics online for a sample */ + class SampleStatistics { + public: + SampleStatistics() = default; + + /* given we have a sample S(n) of size n with given mean, + * compute mean of sample with one event x added + * + * n. #of samples *preceding* x + */ + static double update_online_mean(double x, uint32_t n, double mean) { + /* to update mean in a numerically stable way: + * avoid computing running sample sum, to avoid + * adding floating point numbers with distant magnitudes; + * instead compute correction to the mean directly + * + * n / x(i) \ + * mean(Sn) := Sum | ----- | + * i=1 \ n / + * + * so + * n+1 / x(i) \ + * mean(S(n+1)) = Sum | ----- | + * i=1 \ n+1 / + * + * n n+1 / x(i) \ + * = --- Sum | ----- | + * n+1 i=1 \ n / + * + * n / x(n+1) n x(i) \ + * = --- | ------ + Sum ---- | + * n+1 \ n i=1 n / + * + * x(n+1) / n \ + * = ------ + | --- . mean(S(n)) | + * n+1 \ n+1 / + * + * x(n+1) / -1 \ + * = ------ + mean(S(n)) + | --- . mean(S(n)) | + * n+1 \ n+1 / + * + * = mean(S(n)) + (x(n+1) - mean(S(n))) / (n+1) + */ + return mean + ((1.0 / (n+1)) * (x - mean)); + } /*update_online_mean*/ + + /* + * with S(n) = Sn = {set of n samples}, + * u(n) = mean(Sn) + * + * (with mean, variance meaning "estimate for") + * + * 1 n / 2 \ / 1 \ 2 + * variance(Sn) := --- . Sum | (x(i) | - | --- . Sum x(i) | + * n i=1 \ / \ n i=1 / + * + * using Welford's recurrence for 2nd moment: + * + * define + * M2(n+1) := M2(n) + (x(n+1) - mean(S(n))) + * . (x(n+1) - mean(S(n+1)) + * + * then unbiased variance estimate for S(n+1) is: + * + * M2(n+1) + * ------- + * n + * + * x. new sample value + * mean_np1. mean estimate for S(n+1) + * mean_n. mean estimate for S(n) + * moment2. 2nd moment for S(n) + */ + static double update_online_moment2(double x, + double mean_np1, double mean_n, + double moment2) + { + return moment2 + (x - mean_n) * (x - mean_np1); + } /*update_online_moment2*/ + + uint32_t n_sample() const { return n_sample_; } + double mean() const { return mean_; } + double moment2() const { return moment2_; } + /* 'sample variance' = variance estimate, + * applying Bessel correction for sample bias + * + * require: n_sample >= 2 + */ + double sample_variance() const { return moment2_ / (n_sample_ - 1); } + + /* biased variance estimate + * = (1 - 1/(n+1)) * .sample_variance() + * + * .variance() -> .sample_variance() as sample size -> +oo + * + * require: n_sample >= 1 + */ + double variance() const { return moment2_ / n_sample_; } + + void include_sample(double x) { + /* n+1 */ + uint32_t np1 = this->n_sample_ + 1; + + double mean_np1 = update_online_mean(x, this->n_sample_, this->mean_); + double moment2_np1 = update_online_moment2(x, this->mean_, mean_np1, this->moment2_); + + this->n_sample_ = np1; + this->mean_ = mean_np1; + this->moment2_ = moment2_np1; + } /*include_sample*/ + + private: + uint32_t n_sample_ = 0; + /* estimated mean */ + double mean_ = 0.0; + /* estimated 2nd moment E[X^2] */ + double moment2_ = 0.0; + }; /*SampleStatistics*/ + } /*namespace statistics*/ +} /*namespace xo*/ + +/* end SampleStatistics.hpp */ From e1f39b7b0de413a6522905a971f843e9cee91dcb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 11:11:56 -0400 Subject: [PATCH 0385/2524] initial implementation --- CMakeLists.txt | 47 ++ EigenUtil.cpp | 157 ---- KalmanFilter.cpp | 78 -- KalmanFilter.hpp | 128 ---- KalmanFilterEngine.cpp | 360 --------- KalmanFilterEngine.hpp | 185 ----- KalmanFilterInput.cpp | 118 --- KalmanFilterInput.hpp | 121 --- KalmanFilterInputCallback.hpp | 14 - KalmanFilterInputSource.hpp | 28 - KalmanFilterInputToConsole.hpp | 30 - KalmanFilterObservable.hpp | 110 --- KalmanFilterOutputCallback.hpp | 15 - KalmanFilterSpec.cpp | 27 - KalmanFilterSpec.hpp | 86 --- KalmanFilterState.cpp | 231 ------ KalmanFilterState.hpp | 163 ----- KalmanFilterStateToConsole.hpp | 30 - KalmanFilterStep.cpp | 84 --- KalmanFilterSvc.hpp | 64 -- KalmanFilterTransition.cpp | 55 -- KalmanFilterTransition.hpp | 62 -- cmake/xo_kalmanfilterConfig.cmake.in | 17 + .../xo/kalmanfilter/EigenUtil.hpp | 0 include/xo/kalmanfilter/KalmanFilter.hpp | 127 ++++ .../xo/kalmanfilter/KalmanFilterEngine.hpp | 185 +++++ include/xo/kalmanfilter/KalmanFilterInput.hpp | 121 +++ .../KalmanFilterInputCallback.hpp | 14 + .../kalmanfilter/KalmanFilterInputSource.hpp | 27 + .../KalmanFilterInputToConsole.hpp | 30 + .../kalmanfilter/KalmanFilterObservable.hpp | 109 +++ .../KalmanFilterOutputCallback.hpp | 15 + include/xo/kalmanfilter/KalmanFilterSpec.hpp | 86 +++ include/xo/kalmanfilter/KalmanFilterState.hpp | 163 +++++ .../KalmanFilterStateToConsole.hpp | 30 + .../xo/kalmanfilter/KalmanFilterStep.hpp | 0 include/xo/kalmanfilter/KalmanFilterSvc.hpp | 64 ++ .../kalmanfilter/KalmanFilterTransition.hpp | 62 ++ include/xo/kalmanfilter/init_filter.hpp | 20 + include/xo/kalmanfilter/print_eigen.hpp | 41 ++ init_filter.cpp | 51 -- init_filter.hpp | 20 - print_eigen.hpp | 41 -- src/kalmanfilter/CMakeLists.txt | 31 + src/kalmanfilter/EigenUtil.cpp | 157 ++++ src/kalmanfilter/KalmanFilter.cpp | 78 ++ src/kalmanfilter/KalmanFilterEngine.cpp | 360 +++++++++ src/kalmanfilter/KalmanFilterInput.cpp | 118 +++ .../KalmanFilterInputToConsole.cpp | 2 +- .../kalmanfilter/KalmanFilterObservable.cpp | 2 +- src/kalmanfilter/KalmanFilterSpec.cpp | 27 + src/kalmanfilter/KalmanFilterState.cpp | 231 ++++++ .../KalmanFilterStateToConsole.cpp | 2 +- src/kalmanfilter/KalmanFilterStep.cpp | 84 +++ .../kalmanfilter/KalmanFilterSvc.cpp | 0 src/kalmanfilter/KalmanFilterTransition.cpp | 55 ++ src/kalmanfilter/init_filter.cpp | 52 ++ utest/CMakeLists.txt | 54 ++ utest/KalmanFilter.test.cpp | 690 ++++++++++++++++++ utest/filter_utest_main.cpp | 6 + 60 files changed, 3104 insertions(+), 2261 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 EigenUtil.cpp delete mode 100644 KalmanFilter.cpp delete mode 100644 KalmanFilter.hpp delete mode 100644 KalmanFilterEngine.cpp delete mode 100644 KalmanFilterEngine.hpp delete mode 100644 KalmanFilterInput.cpp delete mode 100644 KalmanFilterInput.hpp delete mode 100644 KalmanFilterInputCallback.hpp delete mode 100644 KalmanFilterInputSource.hpp delete mode 100644 KalmanFilterInputToConsole.hpp delete mode 100644 KalmanFilterObservable.hpp delete mode 100644 KalmanFilterOutputCallback.hpp delete mode 100644 KalmanFilterSpec.cpp delete mode 100644 KalmanFilterSpec.hpp delete mode 100644 KalmanFilterState.cpp delete mode 100644 KalmanFilterState.hpp delete mode 100644 KalmanFilterStateToConsole.hpp delete mode 100644 KalmanFilterStep.cpp delete mode 100644 KalmanFilterSvc.hpp delete mode 100644 KalmanFilterTransition.cpp delete mode 100644 KalmanFilterTransition.hpp create mode 100644 cmake/xo_kalmanfilterConfig.cmake.in rename EigenUtil.hpp => include/xo/kalmanfilter/EigenUtil.hpp (100%) create mode 100644 include/xo/kalmanfilter/KalmanFilter.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterEngine.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterInput.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterInputCallback.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterInputSource.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterInputToConsole.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterObservable.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterOutputCallback.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterSpec.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterState.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterStateToConsole.hpp rename KalmanFilterStep.hpp => include/xo/kalmanfilter/KalmanFilterStep.hpp (100%) create mode 100644 include/xo/kalmanfilter/KalmanFilterSvc.hpp create mode 100644 include/xo/kalmanfilter/KalmanFilterTransition.hpp create mode 100644 include/xo/kalmanfilter/init_filter.hpp create mode 100644 include/xo/kalmanfilter/print_eigen.hpp delete mode 100644 init_filter.cpp delete mode 100644 init_filter.hpp delete mode 100644 print_eigen.hpp create mode 100644 src/kalmanfilter/CMakeLists.txt create mode 100644 src/kalmanfilter/EigenUtil.cpp create mode 100644 src/kalmanfilter/KalmanFilter.cpp create mode 100644 src/kalmanfilter/KalmanFilterEngine.cpp create mode 100644 src/kalmanfilter/KalmanFilterInput.cpp rename KalmanFilterInputToConsole.cpp => src/kalmanfilter/KalmanFilterInputToConsole.cpp (93%) rename KalmanFilterObservable.cpp => src/kalmanfilter/KalmanFilterObservable.cpp (98%) create mode 100644 src/kalmanfilter/KalmanFilterSpec.cpp create mode 100644 src/kalmanfilter/KalmanFilterState.cpp rename KalmanFilterStateToConsole.cpp => src/kalmanfilter/KalmanFilterStateToConsole.cpp (93%) create mode 100644 src/kalmanfilter/KalmanFilterStep.cpp rename KalmanFilterSvc.cpp => src/kalmanfilter/KalmanFilterSvc.cpp (100%) create mode 100644 src/kalmanfilter/KalmanFilterTransition.cpp create mode 100644 src/kalmanfilter/init_filter.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/KalmanFilter.test.cpp create mode 100644 utest/filter_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..f4120636 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,47 @@ +# xo-kalmanfilter/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_kalmanfilter VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) + +# ---------------------------------------------------------------- +# 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 + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(src/kalmanfilter) +add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support for reactor customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/EigenUtil.cpp b/EigenUtil.cpp deleted file mode 100644 index f1d3c4a3..00000000 --- a/EigenUtil.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/* file EigenUtil.cpp - * - * author: Roland Conybeare, Sep 2022 - */ - -#include "EigenUtil.hpp" -#include "printjson/PrintJson.hpp" -#include "reflect/Reflect.hpp" -#include -#include -#include -#include - -namespace xo { - using xo::json::PrintJson; - using xo::json::JsonPrinter; - using xo::reflect::Reflect; - using xo::reflect::TypeDescr; - using VectorXb = Eigen::Array; - using Eigen::VectorXd; - using Eigen::MatrixXd; - -#ifdef NOT_YET - namespace reflect { - template - using EigenVectorX_Tdx = xo::reflect::StlVectorTdx>; - - /* probably need this to appear before decl for class xo::reflect::Reflect */ - template - class EstablishTdx> { - public: - static std::unique_ptr make() { - return EigenVectorX_Tdx::make(); - } /*make*/ - }; /*EstablishTdx*/ - } /*reflect*/ -#endif - - namespace eigen { - - namespace { - /* prints a VectorXd as json, in the obvious format, e.g. - * [1,2,3] - */ - template - class EigenVectorJsonPrinter : public JsonPrinter { - public: - EigenVectorJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} - - virtual void print_json(TaggedPtr tp, - std::ostream * p_os) const override - { - EigenVectorType * pv = this->check_recover_native(tp, p_os); - - if (pv) { - /* EigenVectorType (VectorXb, VectorXd, ..) - * is reflected as atomic for now, out of expedience. - * - * as soon as we reflect as mt_vector, will not need this helper. - */ - *p_os << "["; - - for (std::uint32_t i = 0, n = pv->size(); i < n; ++i) { - if (i > 0) - *p_os << ","; - - /* note: need to dispatch via json printer for vector elements, - * to get special treatment for non-finite values - */ - this->pjson()->print((*pv)[i], p_os); - //*p_os << jsonp((*pv)[i], this->pjson()); - } - - *p_os << "]"; - } - } /*print_json*/ - }; /*EigenVectorJsonPrinter*/ - - /* prints a MatrixXd as json, in row-major format, e.g. - * [[1,2,3], [4,5,6], [7,8,9]] - */ - class MatrixXdJsonPrinter : public JsonPrinter { - public: - MatrixXdJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} - - virtual void print_json(TaggedPtr tp, - std::ostream * p_os) const override - { - MatrixXd * pm = this->check_recover_native(tp, p_os); - - if (pm) { - /* MatrixXd is reflected as atomic for now, out of expedience */ - *p_os << "["; - - for(std::uint32_t i=0, m=pm->rows(); i 0) - *p_os << ", "; - *p_os << "["; - for(std::uint32_t j=0, n=pm->cols(); j 0) - *p_os << ","; - - /* note: need to dispatch via json printer for matrix elements, - * to get special treatment for non-finite values - */ - this->pjson()->print((*pm)(i, j), p_os); - //*p_os << jsonp((*pm)(i, j), this->pjson()); - } - *p_os << "]"; - } - - *p_os << "]"; - } - } /*print_json*/ - }; /*MatrixXdJsonPrinter*/ - - template - void - provide_eigen_vector_printer(PrintJson * p_pjson) - { - TypeDescr td = Reflect::require(); - std::unique_ptr pr(new EigenVectorJsonPrinter(p_pjson)); - - p_pjson->provide_printer(td, std::move(pr)); - } /*provide_eigen_vector_printer*/ - } /*namespace*/ - - void - EigenUtil::reflect_eigen() - { -#ifdef NOT_YET - Reflect::require(); - Reflect::require(); -#endif - } /*reflect_eigen*/ - - void - EigenUtil::provide_json_printers(PrintJson * p_pjson) - { - assert(p_pjson); - - provide_eigen_vector_printer(p_pjson); - provide_eigen_vector_printer(p_pjson); - - { - TypeDescr td = Reflect::require(); - std::unique_ptr pr(new MatrixXdJsonPrinter(p_pjson)); - - p_pjson->provide_printer(td, std::move(pr)); - } - } /*provide_json_printers*/ - } /*namespace eigen*/ -} /*namespace xo*/ - -/* end EigenUtil.cpp */ diff --git a/KalmanFilter.cpp b/KalmanFilter.cpp deleted file mode 100644 index bf94fde2..00000000 --- a/KalmanFilter.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* @file KalmanFilter.cpp */ - -#include "KalmanFilter.hpp" -#include "KalmanFilterEngine.hpp" -#include "print_eigen.hpp" -#include "indentlog/scope.hpp" -#include "Eigen/src/Core/Matrix.h" - -namespace xo { - using xo::time::utc_nanos; - //using logutil::matrix; - using xo::scope; - using xo::tostr; - using xo::xtag; - using Eigen::MatrixXd; - using Eigen::VectorXd; - - namespace kalman { - // ----- KalmanFilter ----- - - KalmanFilter::KalmanFilter(KalmanFilterSpec spec) - : filter_spec_{std::move(spec)}, - state_ext_{filter_spec_.start_ext()} - {} /*ctor*/ - - void - KalmanFilter::notify_input(ref::rp const & input_kp1) - { - scope log(XO_ENTER0(info)); - - /* on entry: - * .state_ext refers to t(k) - * on exit: - * .step refers to t(k+1) - * .state_ext refers to t(k+1) - */ - - log && log(xtag("step_dt", - input_kp1->tkp1() - this->state_ext_->tm())); - - /* establish step inputs for this filter step: - * F(k+1) (system transition matrix) - * Q(k+1) (system noise covariance matrix) - * H(k+1) (observation coupling matrix) - * R(k+1) (observation noise covariance matrix) - * z(k+1) (observation vector) - */ - this->step_ = this->filter_spec_.make_step(this->state_ext_, input_kp1); - - //if (lscope.enabled()) { lscope.log(xtag("step", this->step_)); } - - /* extrapolate filter state to t(k+1), - * and correct based on z(k+1) - */ - this->state_ext_ = KalmanFilterEngine::step(this->step_); - - //if (lscope.enabled()) { lscope.log(xtag("state_ext", this->state_ext_)); } - } /*notify_input*/ - - void - KalmanFilter::display(std::ostream & os) const - { - os << ""; - } /*display*/ - - std::string - KalmanFilter::display_string() const - { - return tostr(*this); - } /*display_string*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilter.cpp */ diff --git a/KalmanFilter.hpp b/KalmanFilter.hpp deleted file mode 100644 index 295a4f7a..00000000 --- a/KalmanFilter.hpp +++ /dev/null @@ -1,128 +0,0 @@ -/* @file KalmanFilter.hpp */ - -#pragma once - -#include "filter/KalmanFilterSpec.hpp" - -namespace xo { - namespace kalman { - /* Specification for an ordinary discrete linear kalman filter. - * - * The filter generates estimates for a process observed at a discrete - * set of times tk in {t0, t1, .., tn} - * - * At each time tk we have the following: - * - * 0. x(0) initial estimate at t(0) - * P(0) initial priors: error covariance matrix for x(0) - * - * 1. x_(k), [n x 1] vector: - * system state, denoted by vector. - * (state is not directly observable, - * filter will attempt to estimate it) - * - * 2. w_(k), [n x 1] vector - * Q(k), [n x n] matrix - * - * w_(k) denotes system noise, - * gaussian with covariance Q(k). - * noise w_(k) is not directly observable. - * - * 3. z(k), [m x 1] vector: - * - * observation vector for time tk - * - * 4. v_(k), [m x 1] vector - * R(k), [m x m] matrix - * - * v_(k) denotes observation errors, - * gaussian with covariance R(k). - * noise v_(k) is not directly observable. - * - * 5. F(k), [n x n] matrix - * state transition matrix - * model system evolves according to: - * - * x_(k+1) = F(x).x_(k) + w_(k) - * - * 6. observations z(k) depend on system state: - * - * z(k) = H(k).x_(k) + v_(k) - * - * 7. Kalman filter outputs: - * x(k), [n x 1] vector - * Q(k), [n x n] matrix - * - * x(k) is optimal estimate for system state x_(k) - * P(k) is covariance matrix specifying confidence intervals - * for pairs (x(k)[i], x(k)[j]) - * - * filter specification consists of: - * n, m, x(0), P(0), F(k), Q(k), H(k), R(k) - * The cardinality of observations z(k) can vary over time, - * so to be precise, m can vary with tk; write as m(k) - * - * More details: - * - avoid having to specify t(k) in advance; - * instead defer until observation available - * so t(k) can be taken from polling timestamp - */ - - /* encapsulate a (linear) kalman filter - * together with event publishing - */ - class KalmanFilter { - public: - using MatrixXd = Eigen::MatrixXd; - using VectorXd = Eigen::VectorXd; - using utc_nanos = xo::time::utc_nanos; - - public: - /* create filter with specification given by spec, and initial state s0 */ - explicit KalmanFilter(KalmanFilterSpec spec); - - uint32_t step_no() const { return state_ext_->step_no(); } - utc_nanos tm() const { return state_ext_->tm(); } - KalmanFilterSpec const & filter_spec() const { return filter_spec_; } - KalmanFilterStep const & step() const { return step_; } - ref::rp const & state_ext() const { return state_ext_; } - - /* notify kalman filter with input for time t(k+1) = input_kp1.tkp1() - * Require: input.tkp1() >= .current_tm() - * Promise: - * - .tm() = input_kp1.tkp1() - * - .step_no() = old .step_no() + 1 - * - .filter_spec_k, .step_k, .state_k updated - * for observations in input_kp1 - */ - void notify_input(ref::rp const & input_kp1); - - void display(std::ostream & os) const; - std::string display_string() const; - - private: - /* specification for kalman filter; - * produces process/observation matrices on demand - */ - KalmanFilterSpec filter_spec_; - - /* filter step for most recent observation */ - KalmanFilterStep step_; - - /* filter state as of most recent observation; - * result of applying KalmanFilterEngine::step() to contents of .step - */ - ref::rp state_ext_; - }; /*KalmanFilter*/ - - inline std::ostream & - operator<<(std::ostream & os, KalmanFilter const & x) { - x.display(os); - return os; - } /*operator<<*/ - - } /*namespace kalman*/ -} /*namespace xo*/ - - -/* end KalmanFilter.hpp */ diff --git a/KalmanFilterEngine.cpp b/KalmanFilterEngine.cpp deleted file mode 100644 index e80f7497..00000000 --- a/KalmanFilterEngine.cpp +++ /dev/null @@ -1,360 +0,0 @@ -/* @file KalmanFilterEngine.cpp - * - */ - -#include "KalmanFilterEngine.hpp" -#include "print_eigen.hpp" -#include "indentlog/scope.hpp" -#include "Eigen/src/Core/Matrix.h" - -namespace xo { - using xo::time::utc_nanos; - using logutil::matrix; - using xo::scope; - using xo::xtag; - using Eigen::LDLT; - using Eigen::MatrixXd; - using Eigen::VectorXd; - - namespace kalman { - // ----- KalmanFilterEngine ----- - - ref::rp - KalmanFilterEngine::extrapolate(utc_nanos tkp1, - ref::rp const & s, - KalmanFilterTransition const & f) - { - //constexpr char const * c_self_name - // = "KalmanFilterEngine::extrapolate"; - - /* prior estimates at t(k) */ - VectorXd const & x = s->state_v(); - MatrixXd const & P = s->state_cov(); - - /* model change from t(k) -> t(k+1) */ - MatrixXd const & F = f.transition_mat(); - MatrixXd const & Q = f.transition_cov(); - - if(F.cols() != x.rows()) { - scope log(XO_DEBUG(true /*debug_flag*/)); - - log("error: F*x: expected F.cols=x.rows", - xtag("F.cols", F.cols()), xtag("x.rows", x.rows())); - } - - /* x(k+1|k) */ - VectorXd x_ext = F * x; - - /* P(k+1|k) */ - MatrixXd P_ext = (F * P * F.transpose()) + Q; - - /* creating new state object here - * allows KalmanFilterSvc.is_volatile()=false - */ - - return KalmanFilterState::make(s->step_no() + 1, - tkp1, - std::move(x_ext), - std::move(P_ext), - f); - } /*extrapolate*/ - - VectorXd - KalmanFilterEngine::kalman_gain1(ref::rp const & skp1_ext, - KalmanFilterObservable const & h, - uint32_t j) - { - constexpr bool c_debug_enabled = false; - - scope log(XO_DEBUG(c_debug_enabled)); - - /* P(k+1|k) :: [n x n] */ - MatrixXd const & P_ext = skp1_ext->state_cov(); - - /* H(k) :: [m x n] */ - MatrixXd const & H = h.observable(); - /* R(k) :: [m x m] */ - MatrixXd const & R = h.observable_cov(); - - /* i'th col of H couples element #i of filter state to each member of input z(k); - * j'th row of H couples filter state to j'th observable - * - * Hj :: [1 x n] Hj is a row-vector - */ - auto Hj = H.row(j); - - /* Rjj is the j'th diagonal element of R */ - double Rjj = R(j, j); - - /* T - * M(k) = Hj * P(k+1|k) * Hj + Rjj - * - * M(k) is a [1 x 1] matrix - */ - double m = Hj * (P_ext * Hj.transpose()) + Rjj; - - /* -1 - * M(k) trivial, since M is [1 x 1] - */ - double m_inv = 1.0 / m; - - /* K :: [n x 1] */ - VectorXd K = P_ext * Hj.transpose() * m_inv; - - log && log("result", - xtag("P(k+1|k)", matrix(P_ext)), - xtag("R", matrix(R)), - xtag("m", m)); - - return K; - } /*kalman_gain1*/ - - MatrixXd - KalmanFilterEngine::kalman_gain(ref::rp const & skp1_ext, - KalmanFilterObservable const & h) - { - scope log(XO_DEBUG(false /*debug_enabled*/)); - - /* P(k+1|k) */ - MatrixXd const & P_ext = skp1_ext->state_cov(); - - MatrixXd const & H = h.observable(); - MatrixXd const & R = h.observable_cov(); - - uint32_t m = H.rows(); - uint32_t n = H.cols(); - - if ((P_ext.rows() != n) || (P_ext.cols() != n)) { - std::string err_msg - = tostr("kalman_gain: with dim(H) = [m x n] expect dim(P) = [n x n]", - xtag("m", m), xtag("n", n), - xtag("P.rows", P_ext.rows()), - xtag("P.cols", P_ext.cols())); - - throw std::runtime_error(err_msg); - } - - if ((R.rows() != m) || (R.cols() != m)) { - std::string err_msg - = tostr("kalman_gain: with dim(H) = [m x n] expect dim(R) = [m x m]", - xtag("m", m), xtag("n", n), - xtag("R.rows", R.rows()), xtag("R.cols", R.cols())); - - throw std::runtime_error(err_msg); - } - - /* kalman gain: - * T -1 - * K(k+1) = P(k+1|k).H(k) .M - * - * T / T \ -1 - * = P(k+1|k).H(k) .| H(k).P(k+1|k).H(k) + R(k) | - * \ / - * - * Notes: - * 1. the matrix M being inverted is symmetric, since represents covariances. - * 2. if diagonal of R(k) has no zeroes (i.e. all measurements are subject to error), - * then it must be non-negative definite - * 3. unless observation errors are perfectly correlated, M(k) - * is positive definite. - * 4. even though 3. holds, there may be a nearby non-positive-definite matrix M+dM, - * Factoring M with finite-precision arithmetic solves for M+dM instead of M; - * which may run into difficulty if M is only 'slighlty' +ve definite. - * If necessary add small diagonal correction D to M, - * sufficient to make M+D positive definite. - * This is equivalent to introducing additional - * uncorrelated observation error, so benign from a robustness perspective - * 5. In generally we usually want to avoid fully realizing a matrix inverse. - * In this case need to explicitly compute K as ingredient used to - * correct state covariance later. - * 6. However, if R is diagonal (which is in practice quite likely), - * then it's easy to decompose a suite of vector observations z(k+1) = [z1, ..zm]T - * into separate zi, with dt=0 separating them. - * Can use this to avoid computing the inverse. - * See .kalman_gain1(), .correct1() - * 7. .kalman_gain() works unaltered when H, R have been reindexed - * to exclude outliers/errors; this is true because .kalman_gain() does not - * use the observation vector z[], i.e. operates entirely in the reduced - * reindexed space. - */ - - MatrixXd M = H * P_ext * H.transpose() + R; - - /* will use to write M as: - * - * T T - * M = P .L.D.L .P - * - * where: - * P is a permutation matrix - * L is lower triangular, with unit diagonal - * D is diagonal - */ - LDLT ldlt = M.ldlt(); - - /* solve for the identity matrix to realize the inverse this way */ - MatrixXd I = MatrixXd::Identity(M.rows(), M.cols()); - - /* -1 - * M - */ - MatrixXd M_inv = ldlt.solve(I); - - /* K(k+1) */ - MatrixXd K = P_ext * H.transpose() * M_inv; - - log && log("result", - xtag("k", skp1_ext->step_no()), - xtag("P(k+1|k)", matrix(P_ext)), - xtag("H", matrix(H)), - xtag("R", matrix(R)), - xtag("M", matrix(M)), - xtag("K", matrix(K))); - - return K; - } /*kalman_gain*/ - - ref::rp - KalmanFilterEngine::correct1(ref::rp const & skp1_ext, - KalmanFilterObservable const & h, - ref::rp const & zkp1, - uint32_t j) - { - uint32_t n = skp1_ext->n_state(); - /* Kj :: [n x 1] */ - VectorXd Kj = kalman_gain1(skp1_ext, h, j); - /* H :: [m x n] */ - MatrixXd const & H = h.observable(); - VectorXd const & z = zkp1->z(); - - /* Hj :: [1 x n] the j'th row of H */ - auto const & Hj = H.row(j); - - - /* x(k+1|x) :: [n x 1] */ - VectorXd const & x_ext = skp1_ext->state_v(); - - /* P(k+1|k) :: [n x n] */ - MatrixXd const & P_ext = skp1_ext->state_cov(); - - /* innovj : difference between jth 'actual observation' - * and jth 'predicted observation' - */ - double innovj = z[j] - (Hj * x_ext); - - /* x(k+1) */ - VectorXd xkp1 = x_ext + (Kj * innovj); - - MatrixXd I = MatrixXd::Identity(n, n); - /* note: Kj [n x 1], Hj [1 x n], - * so Kj * Hj [n x n], with rank 1 - */ - MatrixXd Pkp1 = (I - (Kj * Hj)) * P_ext; - - return KalmanFilterStateExt::make(skp1_ext->step_no(), - skp1_ext->tm(), - xkp1, - Pkp1, - skp1_ext->transition(), - Kj, - j, - zkp1); - } /*correct1*/ - - ref::rp - KalmanFilterEngine::correct(ref::rp const & skp1_ext, - KalmanFilterObservable const & h, - ref::rp const & zkp1) - { - uint32_t n = skp1_ext->n_state(); - /* K :: [n x m] */ - MatrixXd K = kalman_gain(skp1_ext, h); - MatrixXd const & H = h.observable(); - /* z_orig[] is original observation vector before reindexing */ - VectorXd const & z_orig = zkp1->z(); - /* reindex z_orig, keeping only elements that appear in - */ - VectorXd z = z_orig(h.keep()); - - /* 'ext' short for 'extrapolated' */ - VectorXd const & x_ext = skp1_ext->state_v(); - MatrixXd const & P_ext = skp1_ext->state_cov(); - - /* innov: difference between 'actual observations' - * and 'predicted observations' - */ - VectorXd innov = z - (H * x_ext); - - /* x(k+1) :: [n x 1] */ - VectorXd xkp1 = x_ext + K * innov; - MatrixXd I = MatrixXd::Identity(n, n); - MatrixXd Pkp1 = (I - K * H) * P_ext; - - return KalmanFilterStateExt::make(skp1_ext->step_no(), - skp1_ext->tm(), - xkp1, - Pkp1, - skp1_ext->transition(), - K, - -1 /*j: not used*/, - zkp1); - } /*correct*/ - - ref::rp - KalmanFilterEngine::step(utc_nanos tkp1, - ref::rp const & sk, - KalmanFilterTransition const & Fk, - KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1) - { - ref::rp skp1_ext - = KalmanFilterEngine::extrapolate(tkp1, sk, Fk); - - ref::rp skp1 - = KalmanFilterEngine::correct(skp1_ext, Hkp1, zkp1); - - return skp1; - } /*step*/ - - ref::rp - KalmanFilterEngine::step(KalmanFilterStep const & step_spec) - { - return step(step_spec.tkp1(), - step_spec.state(), - step_spec.model(), - step_spec.obs(), - step_spec.input()); - } /*step*/ - - ref::rp - KalmanFilterEngine::step1(utc_nanos tkp1, - ref::rp const & sk, - KalmanFilterTransition const & Fk, - KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1, - uint32_t j) - { - ref::rp skp1_ext - = KalmanFilterEngine::extrapolate(tkp1, sk, Fk); - - ref::rp skp1 - = KalmanFilterEngine::correct1(skp1_ext, Hkp1, zkp1, j); - - return skp1; - } /*step1*/ - - ref::rp - KalmanFilterEngine::step1(KalmanFilterStep const & step_spec, - uint32_t j) - { - return step1(step_spec.tkp1(), - step_spec.state(), - step_spec.model(), - step_spec.obs(), - step_spec.input(), - j); - } /*step1*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterEngine.cpp */ diff --git a/KalmanFilterEngine.hpp b/KalmanFilterEngine.hpp deleted file mode 100644 index b8c2f949..00000000 --- a/KalmanFilterEngine.hpp +++ /dev/null @@ -1,185 +0,0 @@ -/* @file KalmanFilterEngine.hpp */ - -#pragma once - -#include "filter/KalmanFilterStep.hpp" -#include "filter/KalmanFilterState.hpp" -#include "filter/KalmanFilterTransition.hpp" -#include "filter/KalmanFilterObservable.hpp" -#include "filter/KalmanFilterInput.hpp" - -namespace xo { - namespace kalman { - class KalmanFilterEngine { - public: - using MatrixXd = Eigen::MatrixXd; - using VectorXd = Eigen::VectorXd; - using utc_nanos = xo::time::utc_nanos; - - public: - /* evolution of system state + account for system noise, - * before incorporating effect of observations z(k+1) - * x(k) --> x(k+1|k) - * P(k) --> P(k+1|k) - * - * tkp1. time t(k+1) assoc'd with step k+1 - * sk. filter state at time tk: - * sk.k = k step # (starts from 0) - * sk.tk = t(k) time t(k) assoc'd with step #k - * sk.x = x(k) estimated state at time tk - * sk.P = P(k) quality of state estimate x(k) - * (error covariance matrix) - * Fk. state transition: - * Fk.F = F(k) state transition matrix - * Fk.Q = Q(k) covariance matrix for system noise - * - * returns propagated state estimate for t(k+1): - * retval.k = k+1 - * retval.tk = t(k+1) = tkp1 - * retval.x = x(k+1|k) - * retval.P = P(k+1|k) - */ - static ref::rp extrapolate(utc_nanos tkp1, - ref::rp const & sk, - KalmanFilterTransition const & Fk); - - /* compute kalman gain matrix for filter step t(k) -> t(k+1) - * Expensive implementation using matrix inversion - * - * T - * M(k+1) = H(k).P(k+1|k).H(k) + R(k) - * - * T -1 - * K(k+1) = P(k+1|k).H(k) .M(k+1) - * - * Require: - * - skp1_ext.n_state() = Hkp1.n_state() - * - * skp1_ext. extrapolated filter state at t(k+1) - * Hkp1. relates model state to observable variables, - * for step t(k) -> t(k+1) - */ - static MatrixXd kalman_gain(ref::rp const & skp1_ext, - KalmanFilterObservable const & Hkp1); - - /* compute kalman gain for a single observation z(k)[j]. - * This is useful iff the observation error matrix R is diagonal. - * For diagonal R we can present a set of observations z(k) serially - * instead of all at once, with lower time complexity - * - * Kalman Filter specifies some space with m observables. - * j identifies one of those observables, indexing from 0. - * This corresponds to row #j of H(k), and element R[j,j] of R. - * - * Effectively, we are projecting the kalman filter assoc'd with - * {skp1_ext, Hkp1} to a filter with a single observable variable z(k)[j], - * then computing the (scalar) kalman gain for this 1-variable filter - * - * The gain vector tells us for each member of filter state, - * how much to adjust our optimal estimate for that member for a unit - * amount of innovation in observable #j, i.e. for difference between - * expected and actual value for that observable. - */ - static VectorXd kalman_gain1(ref::rp const & skp1_ext, - KalmanFilterObservable const & Hkp1, - uint32_t j); - - /* correct extrapolated state+cov estimate; - * also computes kalman gain - * - * Require: - * - skp1_ext.n_state() = Hkp1.n_state() - * - zkp1.n_obs() == Hkp1.n_observable() - * - * skp1_ext. extrapolated kalman state + covaraince at t(k+1) - * Hkp1. relates model state to observable variables - * for step t(k) -> t(k+1) - * zkp1. observations for time t(k+1) - * - * return new filter state+cov - */ - static ref::rp correct(ref::rp const & skp1_ext, - KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1); - - /* correct extrapolated filter state for observation - * of j'th filter observable z(k+1)[j] - * - * Can use this when observation errors are uncorrelated - * (i.e. observation error matrix R is diagonal) - */ - static ref::rp correct1(ref::rp const & skp1_ext, - KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1, - uint32_t j); - - /* step filter from t(k) -> t(k+1) - * - * sk. filter state from previous step: - * x (state vector), P (state covar matrix) - * Fk. transition-related params: - * F (transition matrix), Q (system noise covar matrix) - * Hkp1. observation-related params: - * H (coupling matrix), R (error covar matrix) - * zkp1. observations z(k+1) for time t(k+1) - */ - static ref::rp step(utc_nanos tkp1, - ref::rp const & sk, - KalmanFilterTransition const & Fk, - KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1); - - /* step filter from t(k) -> tk(k+1) - * same as - * .step(tkp1, sk, step_spec.model(), step_spec.obs(), zkp1); - * - * step_spec. encapsulates Fk (transition-related params) - * and Q (system noise covar matrix) - */ - static ref::rp step(KalmanFilterStep const & step_spec); - - /* step filter from t(k) -> t(k+1) - * - * sk. filter state from previous step: - * x (state vector), P (state covar matrix) - * Fk. transition-related params: - * F (transition matrix), Q (system noise covar matrix) - * Hkp1. observation-related params: - * H (coupling matrix), R (error covar matrix) - * zkp1. observations z(k+1) for time t(k+1) - * j. identifies a single filter observable -- - * step will only consume observation z(k+1)[j] - */ - static ref::rp step1(utc_nanos tkp1, - ref::rp const & sk, - KalmanFilterTransition const & Fk, - KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1, - uint32_t j); - - /* step filter from t(k) -> t(k+1) - * - * same as - * .step1(step_spec.tkp1(), - * step_spec.state(), - * step_spec.model(), - * step_spec.obs(), - * step_spec.input(), - * j); - * - * step_spec. encapsulates - * x (state vector), - * P (state covar matrix), - * Fk (transition-related params), - * Q (system noise covar matrix) - * z (z(k+1), observations at time t(k+1)) - * j. identifies a single filter observable -- - * step will only consume observation z(k+1)[j] - */ - static ref::rp step1(KalmanFilterStep const & step_spec, - uint32_t j); - }; /*KalmanFilterEngine*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterEngine.hpp */ diff --git a/KalmanFilterInput.cpp b/KalmanFilterInput.cpp deleted file mode 100644 index 03b5330f..00000000 --- a/KalmanFilterInput.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* @file KalmanFilterInput.cpp */ - -#include "KalmanFilterInput.hpp" -#include "reflect/StructReflector.hpp" -#include "Eigen/src/Core/Matrix.h" -#include "print_eigen.hpp" -#include "indentlog/scope.hpp" -#include "reflect/TaggedRcptr.hpp" - -namespace xo { - using xo::reflect::Reflect; - using xo::reflect::TaggedRcptr; - using xo::reflect::StructReflector; - using xo::scope; - using logutil::matrix; - using xo::xtag; - using Eigen::MatrixXd; - using Eigen::VectorXi; - using std::uint32_t; - - namespace kalman { - ref::rp - KalmanFilterInput::make(utc_nanos tkp1, - VectorXb const & presence, - VectorXd const & z, - VectorXd const & Rd) - { - return new KalmanFilterInput(tkp1, presence, z, Rd); - } /*make*/ - - ref::rp - KalmanFilterInput::make_present(utc_nanos tkp1, - VectorXd const & z) - { - VectorXb presence = VectorXb::Ones(z.cols()); - - return new KalmanFilterInput(tkp1, - presence, - z, - VectorXd(0) /*Rd - not using*/); - } /*make*/ - - VectorXi - KalmanFilterInput::make_kept_index() const - { - scope log(XO_DEBUG(false /*!debug_flag*/)); - - log && log(xtag("presence", matrix(presence_))); - - /* count truth values */ - uint32_t mstar = 0; - - for (uint32_t j = 0, m = this->presence_.rows(); jpresence_[j]) - ++mstar; - } - - log && log(xtag("m*", mstar)); - - VectorXi keep(mstar); - - /* 2nd pass, populate keep[] */ - - uint32_t jstar = 0; - - for (uint32_t j = 0, m = this->presence_.rows(); jpresence_[j]) { - keep[jstar] = j; - ++jstar; - } - } - - log && log("keep", matrix(keep)); - - return keep; - } /*make_kept_index*/ - - void - KalmanFilterInput::reflect_self() - { - StructReflector sr; - - if (sr.is_incomplete()) { - REFLECT_MEMBER(sr, tkp1); - REFLECT_MEMBER(sr, presence); - REFLECT_MEMBER(sr, z); - REFLECT_MEMBER(sr, Rd); - } - } /*reflect_self*/ - - reflect::TaggedRcptr - KalmanFilterInput::self_tp() - { - return Reflect::make_rctp(this); - } /*self_tp*/ - - void - KalmanFilterInput::display(std::ostream & os) const - { - os << ""; - } /*display*/ - - std::string - KalmanFilterInput::display_string() const - { - std::stringstream ss; - this->display(ss); - return ss.str(); - } /*display_string*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterInput.cpp */ diff --git a/KalmanFilterInput.hpp b/KalmanFilterInput.hpp deleted file mode 100644 index a73a72d0..00000000 --- a/KalmanFilterInput.hpp +++ /dev/null @@ -1,121 +0,0 @@ -/* @file KalmanFilterInput.hpp */ - -#pragma once - -#include "reflect/SelfTagging.hpp" -#include "time/Time.hpp" -#include "refcnt/Refcounted.hpp" -#include -#include - -namespace xo { - /* FIXME. hack here to get member access working for reflection */ - namespace vf { class StrikesetKfinput; } - - namespace kalman { - /* represents a single kalman filter input event - * comprising: - * - time tkp1 - * - observation vector z[] - * - presence bits presence[] for z - * - observation errors Rd[] - */ - class KalmanFilterInput : public reflect::SelfTagging { - public: - using TaggedRcptr = xo::reflect::TaggedRcptr; - using utc_nanos = xo::time::utc_nanos; - using VectorXb = Eigen::Array; - using VectorXi = Eigen::VectorXi; - using VectorXd = Eigen::VectorXd; - using uint32_t = std::uint32_t; - - public: - KalmanFilterInput() = default; - - static ref::rp make(utc_nanos tkp1, - VectorXb const & presence, - VectorXd const & z, - VectorXd const & Rd); - - /* create input, with all presence bits set + not using Rd */ - static ref::rp make_present(utc_nanos tkp1, - VectorXd const & z); - - /* reflect KalmanFilterInput object representation */ - static void reflect_self(); - - /* alt name -- concession to reactor::DirectSource - * when event type is KalmanFilterInput - */ - utc_nanos tm() const { return tkp1_; } - - utc_nanos tkp1() const { return tkp1_; } - uint32_t n_obs() const { return z_.size(); } - VectorXb const & presence() const { return presence_; } - VectorXd const & z() const { return z_; } - VectorXd const & Rd() const { return Rd_; } - - /* computes reindexer keep[]: - * .presence[keep[j*]] - * is the j*'th true value in .presence - */ - VectorXi make_kept_index() const; - - virtual void display(std::ostream & os) const; - std::string display_string() const; - - // ----- inherited from SelfTagging ----- - - virtual TaggedRcptr self_tp() override; - - protected: - KalmanFilterInput(utc_nanos tkp1, VectorXb presence, VectorXd z, VectorXd Rd) - : tkp1_(tkp1), - presence_{std::move(presence)}, - z_{std::move(z)}, - Rd_{std::move(Rd)} {} - - friend class xo::vf::StrikesetKfinput; - - private: - /* t(k+1) - asof time for observations .z */ - utc_nanos tkp1_ = xo::time::timeutil::epoch(); - /* [m x 1] presence vector. - * an observation z[j] is present iff .presence[j] is true. - * rows/columns for an absent observation are removed from filter matrices - */ - VectorXb presence_; - /* [m x 1] observation vector z(k) */ - VectorXd z_; - - /* [m x 1] observation error vector Rd(k). - * This represents a side channel for passing the diagonal of - * observation matrix R(k), when both: - * (a) error of different observations are assumed to be uncorrelated (likely) - * (b) error variance is derived from input data, e.g. because of - * some input preprocessing. - * - * It's up to KalmanFilterSpec::MkStepFn to opt-in to using this information, - * which requires agreement with any input preparation step. - * - * This variable could just as well provide observation error matrix R - * - * NOTE: perhaps-cleaner alternative would be to inherit KalmanFilterInput to - * introduce additional state, then MkStepFn can dynamic_cast - */ - VectorXd Rd_; - }; /*KalmanFilterInput*/ - - using KalmanFilterInputPtr = ref::rp; - - inline std::ostream & - operator<<(std::ostream & os, KalmanFilterInput const & x) - { - x.display(os); - return os; - } /*operator<<*/ - - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterInput.hpp */ diff --git a/KalmanFilterInputCallback.hpp b/KalmanFilterInputCallback.hpp deleted file mode 100644 index d9b2f213..00000000 --- a/KalmanFilterInputCallback.hpp +++ /dev/null @@ -1,14 +0,0 @@ -/* @file KalmanFilterInputCallback.hpp */ - -#pragma once - -#include "refcnt/Refcounted.hpp" -#include "filter/KalmanFilter.hpp" - -namespace xo { - namespace kalman { - using KalmanFilterInputCallback = reactor::Sink1>; - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterInputCallback.hpp */ diff --git a/KalmanFilterInputSource.hpp b/KalmanFilterInputSource.hpp deleted file mode 100644 index 30c5cbf1..00000000 --- a/KalmanFilterInputSource.hpp +++ /dev/null @@ -1,28 +0,0 @@ -/* @file KalmanFilterInputSource.hpp */ - -#pragma once - -#include "reactor/EventSource.hpp" -#include "filter/KalmanFilterInputCallback.hpp" - -namespace xo { - namespace kalman { - /* Use: - * rp src = ...; - * rp in_cb = ...; - * - * src->add_callback(in_cb); - * ... - * // src invokes in_cb->notify_input( - * src->remove_callback(in_cb); - */ - using KalmanFilterInputSource = reactor::EventSource< - /*KalmanFilterInput,*/ - KalmanFilterInputCallback - /*&KalmanFilterInputCallback::notify_filter*/ - >; - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterInputSource.hpp */ - diff --git a/KalmanFilterInputToConsole.hpp b/KalmanFilterInputToConsole.hpp deleted file mode 100644 index 7d48ef7e..00000000 --- a/KalmanFilterInputToConsole.hpp +++ /dev/null @@ -1,30 +0,0 @@ -/* @file KalmanFilterInputToConsole.hpp */ - -#pragma once - -#include "reactor/Sink.hpp" -#include "filter/KalmanFilterInput.hpp" - -namespace xo { - namespace kalman { - class KalmanFilterInputToConsole - : public xo::reactor::SinkToConsole> - { - public: - KalmanFilterInputToConsole() = default; - - static ref::rp make(); - - virtual void display(std::ostream & os) const; - //virtual std::string display_string() const; - }; /*KalmanFilterInputToConsole*/ - - inline std::ostream & - operator<<(std::ostream & os, KalmanFilterInputToConsole const & x) { - x.display(os); - return os; - } /*operator<<*/ - } /*namespace option*/ -} /*namespace xo*/ - -/* end KalmanFilterInputToConsole.hpp */ diff --git a/KalmanFilterObservable.hpp b/KalmanFilterObservable.hpp deleted file mode 100644 index 87c4f9d1..00000000 --- a/KalmanFilterObservable.hpp +++ /dev/null @@ -1,110 +0,0 @@ -/* @file KalmanFilterObservable.hpp */ - -#pragma once - -#include "time/Time.hpp" -#include -#include - -namespace xo { - namespace kalman { - class KalmanFilterObservable { - public: - using MatrixXd = Eigen::MatrixXd; - using VectorXi = Eigen::VectorXi; - - public: - KalmanFilterObservable() = default; - - /* keep maps back to canonical observations z(j). - * H, R have been re-indexed - * - * If all m observations are included, then keep will be: - * [0, .., m-1] - */ - KalmanFilterObservable(VectorXi keep, MatrixXd H, MatrixXd R) - : keep_{std::move(keep)}, H_{std::move(H)}, R_{std::move(R)} { - assert(this->check_ok()); - } /*ctor*/ - - /* build KF observable object where keeping all observations */ - static KalmanFilterObservable keep_all(MatrixXd H, MatrixXd R); - - /* build KF observable object. replace H, R with reindexed versions H', R' - * according to indexing vector keep[]. keep[] indexes members of - * observation vector z(k)[j]. Reindexed z', H', R' as follows: - * - * z'[j*] = z[keep[j*]] - * H'[j*, i] = H[keep[j*], i] - * R'[j1*, j2*] = R[keep[j1*], keep[j2*]] - */ - static KalmanFilterObservable reindex(VectorXi keep, MatrixXd H, MatrixXd R); - - uint32_t n_state() const { return H_.cols(); } - uint32_t n_observable() const { return H_.rows(); } - VectorXi const & keep() const { return keep_; } - MatrixXd const & observable() const { return H_; } - MatrixXd const & observable_cov() const { return R_; } - - bool check_ok() const { - uint32_t m = H_.rows(); - bool keep_is_mx1 = (keep_.rows() == m); - bool r_is_mxm = ((R_.cols() == m) && (R_.rows() == m)); - - bool keep_is_well_ordered = true; - - /* members of .keep are non-negative integers, - * in strictly increasing order - */ - int64_t keep_jm1 = -1; - - for (uint32_t j = 0; j < keep_.rows(); ++j) { - if (keep_[j] < 0) - keep_is_well_ordered = false; - if (keep_[j] <= keep_jm1) - keep_is_well_ordered = false; - } - - /* also would like to require: R is +ve definite */ - - return keep_is_mx1 && keep_is_well_ordered && r_is_mxm; - } /*check_ok*/ - - void display(std::ostream & os) const; - std::string display_string() const; - - private: - - private: - /* m: #of observations that survived sanity/error checks - * - * H, R here will have been re-indexed to exclude rejected observations. - * observations z will also have been re-indexed. - * - * If an observation z[j] is excluded, then also exclude: - * - j'th row of H - * - j'th row and j'th column of R - * - j'th element of z - * - * Given re-indexed H, R, the j*'th row of H goes with z[keep[j*]] - */ - - /* [m x 1] maps back to indices in original observation vector */ - VectorXi keep_; - /* [m x n] observation matrix */ - MatrixXd H_; - /* [m x m] covariance matrix for observation noise */ - MatrixXd R_; - }; /*KalmanFilterObservable*/ - - inline std::ostream & - operator<<(std::ostream & os, KalmanFilterObservable const & x) - { - x.display(os); - return os; - } /*operator<<*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterObservable.hpp */ - diff --git a/KalmanFilterOutputCallback.hpp b/KalmanFilterOutputCallback.hpp deleted file mode 100644 index 69fb8484..00000000 --- a/KalmanFilterOutputCallback.hpp +++ /dev/null @@ -1,15 +0,0 @@ -/* @file KalmanFilterOutputCallback.hpp */ - -#pragma once - -#include "reactor/Sink.hpp" -#include "filter/KalmanFilter.hpp" - -namespace xo { - namespace kalman { - /* callback for consuming kalman filter output */ - using KalmanFilterOutputCallback = reactor::Sink1>; - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterOutputCallback.hpp */ diff --git a/KalmanFilterSpec.cpp b/KalmanFilterSpec.cpp deleted file mode 100644 index 296aeb99..00000000 --- a/KalmanFilterSpec.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* @file KalmanFilterSpec.cpp */ - -#include "KalmanFilterSpec.hpp" -#include "indentlog/scope.hpp" - -namespace xo { - using xo::tostr; - using xo::xtag; - - namespace kalman { - void - KalmanFilterSpec::display(std::ostream & os) const - { - os << ""; - } /*display*/ - - std::string - KalmanFilterSpec::display_string() const - { - return tostr(*this); - } /*display_string*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterSpec.cpp */ diff --git a/KalmanFilterSpec.hpp b/KalmanFilterSpec.hpp deleted file mode 100644 index 56ffd783..00000000 --- a/KalmanFilterSpec.hpp +++ /dev/null @@ -1,86 +0,0 @@ -/* @file KalmanFilterSpec.hpp */ - -#pragma once - -#include "filter/KalmanFilterStep.hpp" - -namespace xo { - namespace kalman { - /* full specification for a kalman filter. - * - * For a textbook linear filter, expect a KalmanFilterStep - * instance to be independent of KalmanFilterState/KalmanFilterInput. - * - * Relaxing this requirement for two reasons: - * 1. (proper) want to allow filter with variable timing between observations, - * expecially if observations are event-driven. - * In that case it's likely that state transition matrices are a function - * of elapsed time between observations. Providing filter state sk - * allows MkStepFn to use sk.tm() - * 2. (sketchy) when observations represent market data, - * desirable to treat an observation as giving one-sided information - * about true value. For example treat a bid price as evidence - * true value is higher than that bid, but don't want to constrain - * "how much higher". Certainly no reason to think that - * bid price is normally distributed around fair value. - * Allow for hack in which we - * and modulate error distribution "as if it were normal" to assess - * a non-gaussian error distribution - */ - class KalmanFilterSpec { - public: - /* typical implementation will look something like: - * mk_step(KalmanFilterState const & sk, - * KalmanFilterInputPtr const & zkp1) - * { - * KalmanFilterTransition model = ...; - * KalmanFilterObservable obs = ...; - * - * return KalmanFilterStep(sk, model, obs, zkp1); - * } - */ - using MkStepFn = std::function const & sk, - KalmanFilterInputPtr const & zkp1)>; - - public: - explicit KalmanFilterSpec(ref::rp s0, MkStepFn mkstepfn) - : start_ext_{std::move(s0)}, - mk_step_fn_{std::move(mkstepfn)} {} - - ref::rp const & start_ext() const { return start_ext_; } - /* get step parameters (i.e. matrices F, Q, H, R) - * for step t(k) -> t(k+1). - * - * We supply t(k) state s and t(k+1) observation z(k+1): - * - to allow time stepping to be observation-driven - * - to allow for selective outlier removal - */ - KalmanFilterStep make_step(ref::rp const & sk, - ref::rp const & zkp1) { - return this->mk_step_fn_(sk, zkp1); - } /*make_step*/ - - void display(std::ostream & os) const; - std::string display_string() const; - - private: - /* starting state */ - ref::rp start_ext_; - - /* creates kalman filter step object on demand; - * step object specifies inputs to 1 step in discrete - * linear kalman filter - */ - MkStepFn mk_step_fn_; - }; /*KalmanFilterSpec*/ - - inline std::ostream & - operator<<(std::ostream & os, KalmanFilterSpec const & x) { - x.display(os); - return os; - } /*operator<<*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterSpec.hpp */ diff --git a/KalmanFilterState.cpp b/KalmanFilterState.cpp deleted file mode 100644 index 7656908c..00000000 --- a/KalmanFilterState.cpp +++ /dev/null @@ -1,231 +0,0 @@ -/* @file KalmanFilterState.cpp */ - -#include "KalmanFilterState.hpp" -#include "print_eigen.hpp" -#include "reflect/StructReflector.hpp" -#include "reflect/TaggedPtr.hpp" -#include "indentlog/scope.hpp" -#include "Eigen/src/Core/Matrix.h" -#include -#include - -namespace xo { - using xo::reflect::Reflect; - using xo::reflect::TaggedRcptr; - using xo::reflect::StructReflector; - using xo::time::utc_nanos; - using xo::ref::rp; - using logutil::matrix; - using logutil::vector; - //using xo::scope; - using xo::xtag; - using xo::tostr; - //using Eigen::LDLT; - using Eigen::MatrixXd; - using Eigen::VectorXd; - - namespace kalman { - // ----- KalmanFilterState ----- - - rp - KalmanFilterState::make() - { - return new KalmanFilterState(); - } /*make*/ - - rp - KalmanFilterState::make(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition) - { - return new KalmanFilterState(k, tk, - std::move(x), - std::move(P), - std::move(transition)); - } /*make*/ - - void - KalmanFilterState::reflect_self() - { - StructReflector sr; - - if (sr.is_incomplete()) { - REFLECT_MEMBER(sr, k); - REFLECT_MEMBER(sr, tk); - REFLECT_MEMBER(sr, x); - REFLECT_MEMBER(sr, P); - } - } /*reflect_self*/ - - KalmanFilterState::KalmanFilterState() = default; - - KalmanFilterState::KalmanFilterState(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition) - : k_{k}, tk_{tk}, - x_{std::move(x)}, P_{std::move(P)}, - transition_{std::move(transition)} - {} - - TaggedRcptr - KalmanFilterState::self_tp() - { - return Reflect::make_rctp(this); - } /*self_tp*/ - - // ----- KalmanFilterExt ----- - - rp - KalmanFilterStateExt::make() - { - return new KalmanFilterStateExt(); - } /*make*/ - - rp - KalmanFilterStateExt::make(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition, - MatrixXd K, - int32_t j, - rp zk) - { - return new KalmanFilterStateExt(k, - tk, - std::move(x), - std::move(P), - std::move(transition), - std::move(K), - j, - std::move(zk)); - } /*make*/ - - void - KalmanFilterStateExt::reflect_self() - { - StructReflector sr; - - if (sr.is_incomplete()) { - /* TODO: use sr.adopt_ancestors() */ - - REFLECT_EXPLICIT_MEMBER(sr, "k", &KalmanFilterState::k_); - REFLECT_EXPLICIT_MEMBER(sr, "tk", &KalmanFilterState::tk_); - REFLECT_EXPLICIT_MEMBER(sr, "x", &KalmanFilterState::x_); - REFLECT_EXPLICIT_MEMBER(sr, "P", &KalmanFilterState::P_); - REFLECT_EXPLICIT_MEMBER(sr, "transition", &KalmanFilterState::transition_); - REFLECT_MEMBER(sr, j); - REFLECT_MEMBER(sr, K); - REFLECT_MEMBER(sr, zk); - } - } /*reflect_self*/ - - KalmanFilterStateExt::KalmanFilterStateExt(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition, - MatrixXd K, - int32_t j, - rp zk) - : KalmanFilterState(k, tk, - std::move(x), - std::move(P), - std::move(transition)), - j_{j}, - K_{std::move(K)}, - zk_{std::move(zk)} - { - uint32_t n = x.size(); - - if (n != P.rows() || n != P.cols()) { - std::string err_msg - = tostr("with n=x.size expect [n x n] covar matrix P", - xtag("n", x.size()), - xtag("P.rows", P.rows()), - xtag("P.cols", P.cols())); - - throw std::runtime_error(err_msg); - } - - if ((K.rows() > 0) && (K.rows() > 0)) { - if (n != K.rows()) { - std::string err_msg - = tostr("with n=x.size expect [m x n] gain matrix K", - xtag("n", x.size()), - xtag("K.rows", K.rows()), - xtag("K.cols", K.cols())); - - throw std::runtime_error(err_msg); - } - } else { - /* bypass test with [0 x 0] matrix K; - * normal for initial filter state - */ - } - } /*ctor*/ - - void - KalmanFilterState::display(std::ostream & os) const - { - os << ""; - } /*display*/ - - std::string - KalmanFilterState::display_string() const - { - std::stringstream ss; - ss << *this; - return ss.str(); - } /*display_string*/ - - // ----- KalmanFilterStateExt ----- - - ref::rp - KalmanFilterStateExt::initial(utc_nanos t0, - VectorXd x0, - MatrixXd P0) - { - return KalmanFilterStateExt::make - (0 /*k*/, - t0, - std::move(x0), - std::move(P0), - KalmanFilterTransition(MatrixXd() /*F - not used for initial step*/, - MatrixXd() /*Q - not used for initial step*/), - MatrixXd() /*K - not used for initial step*/, - -1 /*j - not used for initial step*/, - nullptr /*zk - not defined for initial step*/); - } /*initial*/ - - void - KalmanFilterStateExt::display(std::ostream & os) const - { - os << "step_no()) - << xtag("tk", this->tm()) - << xtag("x", matrix(this->state_v())) - << xtag("P", matrix(this->state_cov())) - << xtag("K", matrix(K_)) - << xtag("j", j_) - << ">"; - } /*display*/ - - TaggedRcptr - KalmanFilterStateExt::self_tp() - { - return Reflect::make_rctp(this); - } /*self_tp*/ - } /*namespace filter*/ -} /*namespace xo*/ - -/* end KalmanFilterState.cpp */ diff --git a/KalmanFilterState.hpp b/KalmanFilterState.hpp deleted file mode 100644 index 113c4c6b..00000000 --- a/KalmanFilterState.hpp +++ /dev/null @@ -1,163 +0,0 @@ -/* @file KalmanFilterState.hpp */ - -#pragma once - -#include "reflect/SelfTagging.hpp" -#include "filter/KalmanFilterInput.hpp" -#include "filter/KalmanFilterTransition.hpp" -#include "time/Time.hpp" -#include -#include -#include - -namespace xo { - namespace kalman { - /* encapsulate state (i.e. initial state, and output after each step) - * for a kalman filter - */ - class KalmanFilterState : public reflect::SelfTagging { - public: - using TaggedRcptr = reflect::TaggedRcptr; - using utc_nanos = xo::time::utc_nanos; - using VectorXd = Eigen::VectorXd; - using MatrixXd = Eigen::MatrixXd; - using uint32_t = std::uint32_t; - - public: - static ref::rp make(); - static ref::rp make(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition); - virtual ~KalmanFilterState() = default; - - /* reflect KalmanFilterState object representation */ - static void reflect_self(); - - uint32_t step_no() const { return k_; } - utc_nanos tm() const { return tk_; } - /* with n = .n_state(): - * x_ is [n x 1] vector - * P_ is [n x n] matrix, - */ - uint32_t n_state() const { return x_.size(); } - VectorXd const & state_v() const { return x_; } - MatrixXd const & state_cov() const { return P_; } - - KalmanFilterTransition const & transition() const { return transition_; } - - virtual void display(std::ostream & os) const; - std::string display_string() const; - - // ----- inherited from SelfTagging ----- - - virtual TaggedRcptr self_tp() override; - - private: - KalmanFilterState(); - KalmanFilterState(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition); - - friend class KalmanFilterStateExt; - - private: - /* step# k, advances by +1 on each filter step */ - uint32_t k_ = 0; - /* time t(k) */ - utc_nanos tk_; - /* [n x 1] (estimated) system state xk = x(k) */ - VectorXd x_; - /* [n x n] covariance matrix for error assoc'd with with x(k) - * P(i,j) is the covariance of (ek[i], ek[j]), - * where ex(k) is the difference (x(k) - x_(k)) - * between estimated state x(k) - * (= this->x_) and model state x_(k) - */ - MatrixXd P_; - - /* F, Q matrices driving .x, .P */ - KalmanFilterTransition transition_; - }; /*KalmanFilterState*/ - - inline std::ostream & operator<<(std::ostream & os, - KalmanFilterState const & s) - { - s.display(os); - return os; - } /*operator<<*/ - - /* KalmanFilterStateExt: - * adds additional details from filter step to KalmanFilterState - */ - class KalmanFilterStateExt : public KalmanFilterState { - public: - using TaggedRcptr = reflect::TaggedRcptr; - using MatrixXd = Eigen::MatrixXd; - using int32_t = std::int32_t; - - public: - static ref::rp make(); - static ref::rp make(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition, - MatrixXd K, - int32_t j, - ref::rp zk); - - /* create state object for initial filter state */ - static ref::rp initial(utc_nanos t0, - VectorXd x0, - MatrixXd P0); - - /* reflect KalmanFilterStateExt object representation */ - static void reflect_self(); - - int32_t observable() const { return j_; } - MatrixXd const & gain() const { return K_; } - ref::rp const & zk() const { return zk_; } - - virtual void display(std::ostream & os) const override; - - // ----- inherited from SelfTagging ----- - - virtual TaggedRcptr self_tp() override; - - private: - KalmanFilterStateExt() = default; - KalmanFilterStateExt(uint32_t k, - utc_nanos tk, - VectorXd x, - MatrixXd P, - KalmanFilterTransition transition, - MatrixXd K, - int32_t j, - ref::rp zk); - - private: - /* if -1: not used; - * if >= 0: identifies j'th of m observables; - * gain .K applies just to information obtainable from - * observing that scalar variable - */ - int32_t j_ = -1; - /* if .j is -1: - * [n x n] kalman gain - * if .j >= 0: - * [n x 1] kalman gain for observable #j - */ - MatrixXd K_; - /* input leading to state k. - * empty for initial state (i.e. when .k is 0) - */ - ref::rp zk_; - }; /*KalamnFilterStateExt*/ - } /*namespace filter*/ -} /*namespace xo*/ - -/* end KalmanFilterState.hpp */ diff --git a/KalmanFilterStateToConsole.hpp b/KalmanFilterStateToConsole.hpp deleted file mode 100644 index 0d525cea..00000000 --- a/KalmanFilterStateToConsole.hpp +++ /dev/null @@ -1,30 +0,0 @@ -/* @file KalmanFilterStateToConsole.hpp */ - -#pragma once - -#include "reactor/Sink.hpp" -#include "filter/KalmanFilterState.hpp" - -namespace xo { - namespace kalman { - class KalmanFilterStateToConsole - : public xo::reactor::SinkToConsole - { - public: - KalmanFilterStateToConsole() = default; - - static ref::rp make(); - - virtual void display(std::ostream & os) const; - //virtual std::string display_string() const; - }; /*KalmanFilterStateToConsole*/ - - inline std::ostream & - operator<<(std::ostream & os, KalmanFilterStateToConsole const & x) { - x.display(os); - return os; - } /*operator<<*/ - } /*namespace option*/ -} /*namespace xo*/ - -/* end KalmanFilterStateToConsole.hpp */ diff --git a/KalmanFilterStep.cpp b/KalmanFilterStep.cpp deleted file mode 100644 index f62f2d21..00000000 --- a/KalmanFilterStep.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* @file KalmanFilterStep.cpp */ - -#include "KalmanFilterStep.hpp" -#include "filter/KalmanFilterEngine.hpp" -#include "filter/KalmanFilterState.hpp" -#include "indentlog/scope.hpp" - -namespace xo { - using xo::scope; - using xo::tostr; - using xo::xtag; - using Eigen::MatrixXd; - using Eigen::VectorXd; - - namespace kalman { - ref::rp - KalmanFilterStep::extrapolate() const - { - return KalmanFilterEngine::extrapolate(this->tkp1(), - this->state(), - this->model() /*transition*/); - } /*extrapolate*/ - - MatrixXd - KalmanFilterStep::gain(ref::rp const & skp1_ext) const - { - return KalmanFilterEngine::kalman_gain(skp1_ext, - this->obs()); - } /*gain*/ - - VectorXd - KalmanFilterStep::gain1(ref::rp const & skp1_ext, - uint32_t j) const - { - return KalmanFilterEngine::kalman_gain1(skp1_ext, - this->obs(), - j); - - } /*gain1*/ - - ref::rp - KalmanFilterStep::correct(ref::rp const & skp1_ext) - { - return KalmanFilterEngine::correct(skp1_ext, - this->obs(), - this->input()); - } /*correct*/ - - ref::rp - KalmanFilterStep::correct1(ref::rp const & skp1_ext, - uint32_t j) - { - return KalmanFilterEngine::correct1(skp1_ext, - this->obs(), - this->input(), - j); - } /*correct1*/ - - void - KalmanFilterStep::display(std::ostream & os) const - { - //scope lscope("KalmanFilterStep::display"); - - os << "model()); - //lscope.log("obs:"); - os << xtag("obs", this->obs()); - //lscope.log("input:"); - os << xtag("input", this->input()); - os << ">"; - } /*display*/ - - std::string - KalmanFilterStep::display_string() const - { - return tostr(*this); - } /*display_string*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterStep.cpp */ diff --git a/KalmanFilterSvc.hpp b/KalmanFilterSvc.hpp deleted file mode 100644 index 33d25514..00000000 --- a/KalmanFilterSvc.hpp +++ /dev/null @@ -1,64 +0,0 @@ -/* @file KalmanFilterSvc.hpp */ - -#include "reactor/Sink.hpp" -#include "reactor/DirectSourcePtr.hpp" -#include "filter/KalmanFilter.hpp" -#include "filter/KalmanFilterInputSource.hpp" -#include "filter/KalmanFilterOutputCallback.hpp" -#include "callback/CallbackSet.hpp" - -namespace xo { - namespace kalman { - /* encapsulate a passive KalmanFilter - * instance as an active event consumer+producer - * - * sinks that want to consume KalmanFilterSvc events will use - * .attach_sink() (or .add_callback()) - */ - class KalmanFilterSvc : public xo::reactor::Sink1>, - public xo::reactor::DirectSourcePtr> { - public: - using AbstractSource = xo::reactor::AbstractSource; - - public: - /* named ctor idiom */ - static ref::rp make(KalmanFilterSpec spec); - - KalmanFilter const & filter() const { return filter_; } - - /* notify incoming observations; will trigger kalman filter step */ - void notify_ev(ref::rp const & input_kp1) override; - - // ----- inherited from reactor::AbstractSink ----- - - /* filter captures KF input pointer */ - virtual bool allow_volatile_source() const override { return false; } - virtual uint32_t n_in_ev() const override { return n_in_ev_; } - virtual void display(std::ostream & os) const override; - - // ----- inherited from reactor::AbstractSource ----- - - /* note: correct since KalmanFilterEngine.extrapolate() - * always creates new state object - */ - virtual bool is_volatile() const override { return false; } - - // ----- Inherited from AbstractEventProcessor ----- - - private: - KalmanFilterSvc(KalmanFilterSpec spec); - - private: - /* passive kalman filter */ - KalmanFilter filter_; - /* receive filter input from this source; see .attach_input() */ - ref::rp input_src_; - /* counts lifetime #of input events (see .notify_ev()) */ - uint32_t n_in_ev_ = 0; - /* publish filter state updates to these callbacks */ - fn::RpCallbackSet pub_; - }; /*KalmanFilterSvc*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* KalmanFilterSvc.hpp */ diff --git a/KalmanFilterTransition.cpp b/KalmanFilterTransition.cpp deleted file mode 100644 index 65be627b..00000000 --- a/KalmanFilterTransition.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* @file KalmanFilterTransition.cpp */ - -#include "KalmanFilterTransition.hpp" -#include "reflect/StructReflector.hpp" -#include "print_eigen.hpp" -#include "indentlog/scope.hpp" - -namespace xo { - using xo::reflect::StructReflector; - using logutil::matrix; - using xo::xtag; - - namespace kalman { - void - KalmanFilterTransition::reflect_self() - { - StructReflector sr; - - if (sr.is_incomplete()) { - REFLECT_MEMBER(sr, F); - REFLECT_MEMBER(sr, Q); - } - } /*reflect_self*/ - - uint32_t - KalmanFilterTransition::n_state() const - { - /* we know F.rows() == F.cols() = Q.cols() == Q.rows(), - * see .check_ok() - */ - - return F_.rows(); - } /*n_state*/ - - void - KalmanFilterTransition::display(std::ostream & os) const - { - os << ""; - } /*display*/ - - std::string - KalmanFilterTransition::display_string() const - { - std::stringstream ss; - this->display(ss); - return ss.str(); - } /*display_string*/ - - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterTransition.cpp */ diff --git a/KalmanFilterTransition.hpp b/KalmanFilterTransition.hpp deleted file mode 100644 index fc777c00..00000000 --- a/KalmanFilterTransition.hpp +++ /dev/null @@ -1,62 +0,0 @@ -/* @file KalmanFilterTransition.hpp */ - -#pragma once - -#include "time/Time.hpp" -#include -#include - -namespace xo { - namespace kalman { - - /* encapsulate transition behavior for a kalman filter - * before taking observations into account - */ - class KalmanFilterTransition { - public: - using MatrixXd = Eigen::MatrixXd; - using uint32_t = std::uint32_t; - - public: - KalmanFilterTransition() = default; - KalmanFilterTransition(MatrixXd F, - MatrixXd Q) - : F_{std::move(F)}, Q_{std::move(Q)} { assert(this->check_ok()); } - - static void reflect_self(); - - /* n: cardinality of state vector */ - uint32_t n_state() const; - - MatrixXd const & transition_mat() const { return F_; } - MatrixXd const & transition_cov() const { return Q_; } - - bool check_ok() const { - uint32_t n = F_.rows(); - bool f_is_nxn = ((F_.rows() == n) && (F_.cols() == n)); - bool q_is_nxn = ((Q_.rows() == n) && (Q_.cols() == n)); - - /* also would like to require: Q is +ve definite */ - - return f_is_nxn && q_is_nxn; - } /*check_ok*/ - - void display(std::ostream & os) const; - std::string display_string() const; - - private: - /* [n x n] state transition matrix */ - MatrixXd F_; - /* [n x n] covariance matrix for system noise */ - MatrixXd Q_; - }; /*KalmanFilterTransition*/ - - inline std::ostream & - operator<<(std::ostream & os, KalmanFilterTransition const & x) { - x.display(os); - return os; - } /*operator<<*/ - } /*namespace kalman*/ -} /*namespace xo*/ - -/* end KalmanFilterTransition.hpp */ diff --git a/cmake/xo_kalmanfilterConfig.cmake.in b/cmake/xo_kalmanfilterConfig.cmake.in new file mode 100644 index 00000000..366e45ba --- /dev/null +++ b/cmake/xo_kalmanfilterConfig.cmake.in @@ -0,0 +1,17 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +find_dependency(reactor) +find_dependnecy(eigen3) +#find_dependency(reflect) +#find_dependency(webutil) +#find_dependency(printjson) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/EigenUtil.hpp b/include/xo/kalmanfilter/EigenUtil.hpp similarity index 100% rename from EigenUtil.hpp rename to include/xo/kalmanfilter/EigenUtil.hpp diff --git a/include/xo/kalmanfilter/KalmanFilter.hpp b/include/xo/kalmanfilter/KalmanFilter.hpp new file mode 100644 index 00000000..0df87a61 --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilter.hpp @@ -0,0 +1,127 @@ +/* @file KalmanFilter.hpp */ + +#pragma once + +#include "KalmanFilterSpec.hpp" + +namespace xo { + namespace kalman { + /* Specification for an ordinary discrete linear kalman filter. + * + * The filter generates estimates for a process observed at a discrete + * set of times tk in {t0, t1, .., tn} + * + * At each time tk we have the following: + * + * 0. x(0) initial estimate at t(0) + * P(0) initial priors: error covariance matrix for x(0) + * + * 1. x_(k), [n x 1] vector: + * system state, denoted by vector. + * (state is not directly observable, + * filter will attempt to estimate it) + * + * 2. w_(k), [n x 1] vector + * Q(k), [n x n] matrix + * + * w_(k) denotes system noise, + * gaussian with covariance Q(k). + * noise w_(k) is not directly observable. + * + * 3. z(k), [m x 1] vector: + * + * observation vector for time tk + * + * 4. v_(k), [m x 1] vector + * R(k), [m x m] matrix + * + * v_(k) denotes observation errors, + * gaussian with covariance R(k). + * noise v_(k) is not directly observable. + * + * 5. F(k), [n x n] matrix + * state transition matrix + * model system evolves according to: + * + * x_(k+1) = F(x).x_(k) + w_(k) + * + * 6. observations z(k) depend on system state: + * + * z(k) = H(k).x_(k) + v_(k) + * + * 7. Kalman filter outputs: + * x(k), [n x 1] vector + * Q(k), [n x n] matrix + * + * x(k) is optimal estimate for system state x_(k) + * P(k) is covariance matrix specifying confidence intervals + * for pairs (x(k)[i], x(k)[j]) + * + * filter specification consists of: + * n, m, x(0), P(0), F(k), Q(k), H(k), R(k) + * The cardinality of observations z(k) can vary over time, + * so to be precise, m can vary with tk; write as m(k) + * + * More details: + * - avoid having to specify t(k) in advance; + * instead defer until observation available + * so t(k) can be taken from polling timestamp + */ + + /* encapsulate a (linear) kalman filter + * together with event publishing + */ + class KalmanFilter { + public: + using MatrixXd = Eigen::MatrixXd; + using VectorXd = Eigen::VectorXd; + using utc_nanos = xo::time::utc_nanos; + + public: + /* create filter with specification given by spec, and initial state s0 */ + explicit KalmanFilter(KalmanFilterSpec spec); + + uint32_t step_no() const { return state_ext_->step_no(); } + utc_nanos tm() const { return state_ext_->tm(); } + KalmanFilterSpec const & filter_spec() const { return filter_spec_; } + KalmanFilterStep const & step() const { return step_; } + ref::rp const & state_ext() const { return state_ext_; } + + /* notify kalman filter with input for time t(k+1) = input_kp1.tkp1() + * Require: input.tkp1() >= .current_tm() + * Promise: + * - .tm() = input_kp1.tkp1() + * - .step_no() = old .step_no() + 1 + * - .filter_spec_k, .step_k, .state_k updated + * for observations in input_kp1 + */ + void notify_input(ref::rp const & input_kp1); + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* specification for kalman filter; + * produces process/observation matrices on demand + */ + KalmanFilterSpec filter_spec_; + + /* filter step for most recent observation */ + KalmanFilterStep step_; + + /* filter state as of most recent observation; + * result of applying KalmanFilterEngine::step() to contents of .step + */ + ref::rp state_ext_; + }; /*KalmanFilter*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilter const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilter.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterEngine.hpp b/include/xo/kalmanfilter/KalmanFilterEngine.hpp new file mode 100644 index 00000000..ce21bf3c --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterEngine.hpp @@ -0,0 +1,185 @@ +/* @file KalmanFilterEngine.hpp */ + +#pragma once + +#include "KalmanFilterStep.hpp" +#include "KalmanFilterState.hpp" +#include "KalmanFilterTransition.hpp" +#include "KalmanFilterObservable.hpp" +#include "KalmanFilterInput.hpp" + +namespace xo { + namespace kalman { + class KalmanFilterEngine { + public: + using MatrixXd = Eigen::MatrixXd; + using VectorXd = Eigen::VectorXd; + using utc_nanos = xo::time::utc_nanos; + + public: + /* evolution of system state + account for system noise, + * before incorporating effect of observations z(k+1) + * x(k) --> x(k+1|k) + * P(k) --> P(k+1|k) + * + * tkp1. time t(k+1) assoc'd with step k+1 + * sk. filter state at time tk: + * sk.k = k step # (starts from 0) + * sk.tk = t(k) time t(k) assoc'd with step #k + * sk.x = x(k) estimated state at time tk + * sk.P = P(k) quality of state estimate x(k) + * (error covariance matrix) + * Fk. state transition: + * Fk.F = F(k) state transition matrix + * Fk.Q = Q(k) covariance matrix for system noise + * + * returns propagated state estimate for t(k+1): + * retval.k = k+1 + * retval.tk = t(k+1) = tkp1 + * retval.x = x(k+1|k) + * retval.P = P(k+1|k) + */ + static ref::rp extrapolate(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk); + + /* compute kalman gain matrix for filter step t(k) -> t(k+1) + * Expensive implementation using matrix inversion + * + * T + * M(k+1) = H(k).P(k+1|k).H(k) + R(k) + * + * T -1 + * K(k+1) = P(k+1|k).H(k) .M(k+1) + * + * Require: + * - skp1_ext.n_state() = Hkp1.n_state() + * + * skp1_ext. extrapolated filter state at t(k+1) + * Hkp1. relates model state to observable variables, + * for step t(k) -> t(k+1) + */ + static MatrixXd kalman_gain(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1); + + /* compute kalman gain for a single observation z(k)[j]. + * This is useful iff the observation error matrix R is diagonal. + * For diagonal R we can present a set of observations z(k) serially + * instead of all at once, with lower time complexity + * + * Kalman Filter specifies some space with m observables. + * j identifies one of those observables, indexing from 0. + * This corresponds to row #j of H(k), and element R[j,j] of R. + * + * Effectively, we are projecting the kalman filter assoc'd with + * {skp1_ext, Hkp1} to a filter with a single observable variable z(k)[j], + * then computing the (scalar) kalman gain for this 1-variable filter + * + * The gain vector tells us for each member of filter state, + * how much to adjust our optimal estimate for that member for a unit + * amount of innovation in observable #j, i.e. for difference between + * expected and actual value for that observable. + */ + static VectorXd kalman_gain1(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1, + uint32_t j); + + /* correct extrapolated state+cov estimate; + * also computes kalman gain + * + * Require: + * - skp1_ext.n_state() = Hkp1.n_state() + * - zkp1.n_obs() == Hkp1.n_observable() + * + * skp1_ext. extrapolated kalman state + covaraince at t(k+1) + * Hkp1. relates model state to observable variables + * for step t(k) -> t(k+1) + * zkp1. observations for time t(k+1) + * + * return new filter state+cov + */ + static ref::rp correct(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1); + + /* correct extrapolated filter state for observation + * of j'th filter observable z(k+1)[j] + * + * Can use this when observation errors are uncorrelated + * (i.e. observation error matrix R is diagonal) + */ + static ref::rp correct1(ref::rp const & skp1_ext, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1, + uint32_t j); + + /* step filter from t(k) -> t(k+1) + * + * sk. filter state from previous step: + * x (state vector), P (state covar matrix) + * Fk. transition-related params: + * F (transition matrix), Q (system noise covar matrix) + * Hkp1. observation-related params: + * H (coupling matrix), R (error covar matrix) + * zkp1. observations z(k+1) for time t(k+1) + */ + static ref::rp step(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1); + + /* step filter from t(k) -> tk(k+1) + * same as + * .step(tkp1, sk, step_spec.model(), step_spec.obs(), zkp1); + * + * step_spec. encapsulates Fk (transition-related params) + * and Q (system noise covar matrix) + */ + static ref::rp step(KalmanFilterStep const & step_spec); + + /* step filter from t(k) -> t(k+1) + * + * sk. filter state from previous step: + * x (state vector), P (state covar matrix) + * Fk. transition-related params: + * F (transition matrix), Q (system noise covar matrix) + * Hkp1. observation-related params: + * H (coupling matrix), R (error covar matrix) + * zkp1. observations z(k+1) for time t(k+1) + * j. identifies a single filter observable -- + * step will only consume observation z(k+1)[j] + */ + static ref::rp step1(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1, + uint32_t j); + + /* step filter from t(k) -> t(k+1) + * + * same as + * .step1(step_spec.tkp1(), + * step_spec.state(), + * step_spec.model(), + * step_spec.obs(), + * step_spec.input(), + * j); + * + * step_spec. encapsulates + * x (state vector), + * P (state covar matrix), + * Fk (transition-related params), + * Q (system noise covar matrix) + * z (z(k+1), observations at time t(k+1)) + * j. identifies a single filter observable -- + * step will only consume observation z(k+1)[j] + */ + static ref::rp step1(KalmanFilterStep const & step_spec, + uint32_t j); + }; /*KalmanFilterEngine*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterEngine.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterInput.hpp b/include/xo/kalmanfilter/KalmanFilterInput.hpp new file mode 100644 index 00000000..70fee5af --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterInput.hpp @@ -0,0 +1,121 @@ +/* @file KalmanFilterInput.hpp */ + +#pragma once + +#include "xo/reflect/SelfTagging.hpp" +//#include "time/Time.hpp" +//#include "xo/refcnt/Refcounted.hpp" +#include +#include + +namespace xo { + /* FIXME. hack here to get member access working for reflection */ + namespace vf { class StrikesetKfinput; } + + namespace kalman { + /* represents a single kalman filter input event + * comprising: + * - time tkp1 + * - observation vector z[] + * - presence bits presence[] for z + * - observation errors Rd[] + */ + class KalmanFilterInput : public reflect::SelfTagging { + public: + using TaggedRcptr = xo::reflect::TaggedRcptr; + using utc_nanos = xo::time::utc_nanos; + using VectorXb = Eigen::Array; + using VectorXi = Eigen::VectorXi; + using VectorXd = Eigen::VectorXd; + using uint32_t = std::uint32_t; + + public: + KalmanFilterInput() = default; + + static ref::rp make(utc_nanos tkp1, + VectorXb const & presence, + VectorXd const & z, + VectorXd const & Rd); + + /* create input, with all presence bits set + not using Rd */ + static ref::rp make_present(utc_nanos tkp1, + VectorXd const & z); + + /* reflect KalmanFilterInput object representation */ + static void reflect_self(); + + /* alt name -- concession to reactor::DirectSource + * when event type is KalmanFilterInput + */ + utc_nanos tm() const { return tkp1_; } + + utc_nanos tkp1() const { return tkp1_; } + uint32_t n_obs() const { return z_.size(); } + VectorXb const & presence() const { return presence_; } + VectorXd const & z() const { return z_; } + VectorXd const & Rd() const { return Rd_; } + + /* computes reindexer keep[]: + * .presence[keep[j*]] + * is the j*'th true value in .presence + */ + VectorXi make_kept_index() const; + + virtual void display(std::ostream & os) const; + std::string display_string() const; + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override; + + protected: + KalmanFilterInput(utc_nanos tkp1, VectorXb presence, VectorXd z, VectorXd Rd) + : tkp1_(tkp1), + presence_{std::move(presence)}, + z_{std::move(z)}, + Rd_{std::move(Rd)} {} + + friend class xo::vf::StrikesetKfinput; + + private: + /* t(k+1) - asof time for observations .z */ + utc_nanos tkp1_ = xo::time::timeutil::epoch(); + /* [m x 1] presence vector. + * an observation z[j] is present iff .presence[j] is true. + * rows/columns for an absent observation are removed from filter matrices + */ + VectorXb presence_; + /* [m x 1] observation vector z(k) */ + VectorXd z_; + + /* [m x 1] observation error vector Rd(k). + * This represents a side channel for passing the diagonal of + * observation matrix R(k), when both: + * (a) error of different observations are assumed to be uncorrelated (likely) + * (b) error variance is derived from input data, e.g. because of + * some input preprocessing. + * + * It's up to KalmanFilterSpec::MkStepFn to opt-in to using this information, + * which requires agreement with any input preparation step. + * + * This variable could just as well provide observation error matrix R + * + * NOTE: perhaps-cleaner alternative would be to inherit KalmanFilterInput to + * introduce additional state, then MkStepFn can dynamic_cast + */ + VectorXd Rd_; + }; /*KalmanFilterInput*/ + + using KalmanFilterInputPtr = ref::rp; + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterInput const & x) + { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInput.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterInputCallback.hpp b/include/xo/kalmanfilter/KalmanFilterInputCallback.hpp new file mode 100644 index 00000000..df86a16b --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterInputCallback.hpp @@ -0,0 +1,14 @@ +/* @file KalmanFilterInputCallback.hpp */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include "KalmanFilter.hpp" + +namespace xo { + namespace kalman { + using KalmanFilterInputCallback = reactor::Sink1>; + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInputCallback.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterInputSource.hpp b/include/xo/kalmanfilter/KalmanFilterInputSource.hpp new file mode 100644 index 00000000..7e801936 --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterInputSource.hpp @@ -0,0 +1,27 @@ +/* @file KalmanFilterInputSource.hpp */ + +#pragma once + +#include "xo/reactor/EventSource.hpp" +#include "KalmanFilterInputCallback.hpp" + +namespace xo { + namespace kalman { + /* Use: + * rp src = ...; + * rp in_cb = ...; + * + * src->add_callback(in_cb); + * ... + * // src invokes in_cb->notify_input( + * src->remove_callback(in_cb); + */ + using KalmanFilterInputSource = reactor::EventSource< + /*KalmanFilterInput,*/ + KalmanFilterInputCallback + /*&KalmanFilterInputCallback::notify_filter*/ + >; + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInputSource.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterInputToConsole.hpp b/include/xo/kalmanfilter/KalmanFilterInputToConsole.hpp new file mode 100644 index 00000000..7d81226a --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterInputToConsole.hpp @@ -0,0 +1,30 @@ +/* @file KalmanFilterInputToConsole.hpp */ + +#pragma once + +#include "xo/reactor/Sink.hpp" +#include "KalmanFilterInput.hpp" + +namespace xo { + namespace kalman { + class KalmanFilterInputToConsole + : public xo::reactor::SinkToConsole> + { + public: + KalmanFilterInputToConsole() = default; + + static ref::rp make(); + + virtual void display(std::ostream & os) const; + //virtual std::string display_string() const; + }; /*KalmanFilterInputToConsole*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterInputToConsole const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace option*/ +} /*namespace xo*/ + +/* end KalmanFilterInputToConsole.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterObservable.hpp b/include/xo/kalmanfilter/KalmanFilterObservable.hpp new file mode 100644 index 00000000..c3981095 --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterObservable.hpp @@ -0,0 +1,109 @@ +/* @file KalmanFilterObservable.hpp */ + +#pragma once + +//#include "time/Time.hpp" +#include +#include + +namespace xo { + namespace kalman { + class KalmanFilterObservable { + public: + using MatrixXd = Eigen::MatrixXd; + using VectorXi = Eigen::VectorXi; + + public: + KalmanFilterObservable() = default; + + /* keep maps back to canonical observations z(j). + * H, R have been re-indexed + * + * If all m observations are included, then keep will be: + * [0, .., m-1] + */ + KalmanFilterObservable(VectorXi keep, MatrixXd H, MatrixXd R) + : keep_{std::move(keep)}, H_{std::move(H)}, R_{std::move(R)} { + assert(this->check_ok()); + } /*ctor*/ + + /* build KF observable object where keeping all observations */ + static KalmanFilterObservable keep_all(MatrixXd H, MatrixXd R); + + /* build KF observable object. replace H, R with reindexed versions H', R' + * according to indexing vector keep[]. keep[] indexes members of + * observation vector z(k)[j]. Reindexed z', H', R' as follows: + * + * z'[j*] = z[keep[j*]] + * H'[j*, i] = H[keep[j*], i] + * R'[j1*, j2*] = R[keep[j1*], keep[j2*]] + */ + static KalmanFilterObservable reindex(VectorXi keep, MatrixXd H, MatrixXd R); + + uint32_t n_state() const { return H_.cols(); } + uint32_t n_observable() const { return H_.rows(); } + VectorXi const & keep() const { return keep_; } + MatrixXd const & observable() const { return H_; } + MatrixXd const & observable_cov() const { return R_; } + + bool check_ok() const { + uint32_t m = H_.rows(); + bool keep_is_mx1 = (keep_.rows() == m); + bool r_is_mxm = ((R_.cols() == m) && (R_.rows() == m)); + + bool keep_is_well_ordered = true; + + /* members of .keep are non-negative integers, + * in strictly increasing order + */ + int64_t keep_jm1 = -1; + + for (uint32_t j = 0; j < keep_.rows(); ++j) { + if (keep_[j] < 0) + keep_is_well_ordered = false; + if (keep_[j] <= keep_jm1) + keep_is_well_ordered = false; + } + + /* also would like to require: R is +ve definite */ + + return keep_is_mx1 && keep_is_well_ordered && r_is_mxm; + } /*check_ok*/ + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + + private: + /* m: #of observations that survived sanity/error checks + * + * H, R here will have been re-indexed to exclude rejected observations. + * observations z will also have been re-indexed. + * + * If an observation z[j] is excluded, then also exclude: + * - j'th row of H + * - j'th row and j'th column of R + * - j'th element of z + * + * Given re-indexed H, R, the j*'th row of H goes with z[keep[j*]] + */ + + /* [m x 1] maps back to indices in original observation vector */ + VectorXi keep_; + /* [m x n] observation matrix */ + MatrixXd H_; + /* [m x m] covariance matrix for observation noise */ + MatrixXd R_; + }; /*KalmanFilterObservable*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterObservable const & x) + { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterObservable.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterOutputCallback.hpp b/include/xo/kalmanfilter/KalmanFilterOutputCallback.hpp new file mode 100644 index 00000000..cd858606 --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterOutputCallback.hpp @@ -0,0 +1,15 @@ +/* @file KalmanFilterOutputCallback.hpp */ + +#pragma once + +#include "xo/reactor/Sink.hpp" +#include "KalmanFilter.hpp" + +namespace xo { + namespace kalman { + /* callback for consuming kalman filter output */ + using KalmanFilterOutputCallback = reactor::Sink1>; + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterOutputCallback.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterSpec.hpp b/include/xo/kalmanfilter/KalmanFilterSpec.hpp new file mode 100644 index 00000000..306f298a --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterSpec.hpp @@ -0,0 +1,86 @@ +/* @file KalmanFilterSpec.hpp */ + +#pragma once + +#include "KalmanFilterStep.hpp" + +namespace xo { + namespace kalman { + /* full specification for a kalman filter. + * + * For a textbook linear filter, expect a KalmanFilterStep + * instance to be independent of KalmanFilterState/KalmanFilterInput. + * + * Relaxing this requirement for two reasons: + * 1. (proper) want to allow filter with variable timing between observations, + * expecially if observations are event-driven. + * In that case it's likely that state transition matrices are a function + * of elapsed time between observations. Providing filter state sk + * allows MkStepFn to use sk.tm() + * 2. (sketchy) when observations represent market data, + * desirable to treat an observation as giving one-sided information + * about true value. For example treat a bid price as evidence + * true value is higher than that bid, but don't want to constrain + * "how much higher". Certainly no reason to think that + * bid price is normally distributed around fair value. + * Allow for hack in which we + * and modulate error distribution "as if it were normal" to assess + * a non-gaussian error distribution + */ + class KalmanFilterSpec { + public: + /* typical implementation will look something like: + * mk_step(KalmanFilterState const & sk, + * KalmanFilterInputPtr const & zkp1) + * { + * KalmanFilterTransition model = ...; + * KalmanFilterObservable obs = ...; + * + * return KalmanFilterStep(sk, model, obs, zkp1); + * } + */ + using MkStepFn = std::function const & sk, + KalmanFilterInputPtr const & zkp1)>; + + public: + explicit KalmanFilterSpec(ref::rp s0, MkStepFn mkstepfn) + : start_ext_{std::move(s0)}, + mk_step_fn_{std::move(mkstepfn)} {} + + ref::rp const & start_ext() const { return start_ext_; } + /* get step parameters (i.e. matrices F, Q, H, R) + * for step t(k) -> t(k+1). + * + * We supply t(k) state s and t(k+1) observation z(k+1): + * - to allow time stepping to be observation-driven + * - to allow for selective outlier removal + */ + KalmanFilterStep make_step(ref::rp const & sk, + ref::rp const & zkp1) { + return this->mk_step_fn_(sk, zkp1); + } /*make_step*/ + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* starting state */ + ref::rp start_ext_; + + /* creates kalman filter step object on demand; + * step object specifies inputs to 1 step in discrete + * linear kalman filter + */ + MkStepFn mk_step_fn_; + }; /*KalmanFilterSpec*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterSpec const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterSpec.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterState.hpp b/include/xo/kalmanfilter/KalmanFilterState.hpp new file mode 100644 index 00000000..504def9c --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterState.hpp @@ -0,0 +1,163 @@ +/* @file KalmanFilterState.hpp */ + +#pragma once + +#include "xo/reflect/SelfTagging.hpp" +#include "KalmanFilterInput.hpp" +#include "KalmanFilterTransition.hpp" +//#include "time/Time.hpp" +#include +#include +#include + +namespace xo { + namespace kalman { + /* encapsulate state (i.e. initial state, and output after each step) + * for a kalman filter + */ + class KalmanFilterState : public reflect::SelfTagging { + public: + using TaggedRcptr = reflect::TaggedRcptr; + using utc_nanos = xo::time::utc_nanos; + using VectorXd = Eigen::VectorXd; + using MatrixXd = Eigen::MatrixXd; + using uint32_t = std::uint32_t; + + public: + static ref::rp make(); + static ref::rp make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition); + virtual ~KalmanFilterState() = default; + + /* reflect KalmanFilterState object representation */ + static void reflect_self(); + + uint32_t step_no() const { return k_; } + utc_nanos tm() const { return tk_; } + /* with n = .n_state(): + * x_ is [n x 1] vector + * P_ is [n x n] matrix, + */ + uint32_t n_state() const { return x_.size(); } + VectorXd const & state_v() const { return x_; } + MatrixXd const & state_cov() const { return P_; } + + KalmanFilterTransition const & transition() const { return transition_; } + + virtual void display(std::ostream & os) const; + std::string display_string() const; + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override; + + private: + KalmanFilterState(); + KalmanFilterState(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition); + + friend class KalmanFilterStateExt; + + private: + /* step# k, advances by +1 on each filter step */ + uint32_t k_ = 0; + /* time t(k) */ + utc_nanos tk_; + /* [n x 1] (estimated) system state xk = x(k) */ + VectorXd x_; + /* [n x n] covariance matrix for error assoc'd with with x(k) + * P(i,j) is the covariance of (ek[i], ek[j]), + * where ex(k) is the difference (x(k) - x_(k)) + * between estimated state x(k) + * (= this->x_) and model state x_(k) + */ + MatrixXd P_; + + /* F, Q matrices driving .x, .P */ + KalmanFilterTransition transition_; + }; /*KalmanFilterState*/ + + inline std::ostream & operator<<(std::ostream & os, + KalmanFilterState const & s) + { + s.display(os); + return os; + } /*operator<<*/ + + /* KalmanFilterStateExt: + * adds additional details from filter step to KalmanFilterState + */ + class KalmanFilterStateExt : public KalmanFilterState { + public: + using TaggedRcptr = reflect::TaggedRcptr; + using MatrixXd = Eigen::MatrixXd; + using int32_t = std::int32_t; + + public: + static ref::rp make(); + static ref::rp make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + ref::rp zk); + + /* create state object for initial filter state */ + static ref::rp initial(utc_nanos t0, + VectorXd x0, + MatrixXd P0); + + /* reflect KalmanFilterStateExt object representation */ + static void reflect_self(); + + int32_t observable() const { return j_; } + MatrixXd const & gain() const { return K_; } + ref::rp const & zk() const { return zk_; } + + virtual void display(std::ostream & os) const override; + + // ----- inherited from SelfTagging ----- + + virtual TaggedRcptr self_tp() override; + + private: + KalmanFilterStateExt() = default; + KalmanFilterStateExt(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + ref::rp zk); + + private: + /* if -1: not used; + * if >= 0: identifies j'th of m observables; + * gain .K applies just to information obtainable from + * observing that scalar variable + */ + int32_t j_ = -1; + /* if .j is -1: + * [n x n] kalman gain + * if .j >= 0: + * [n x 1] kalman gain for observable #j + */ + MatrixXd K_; + /* input leading to state k. + * empty for initial state (i.e. when .k is 0) + */ + ref::rp zk_; + }; /*KalamnFilterStateExt*/ + } /*namespace filter*/ +} /*namespace xo*/ + +/* end KalmanFilterState.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterStateToConsole.hpp b/include/xo/kalmanfilter/KalmanFilterStateToConsole.hpp new file mode 100644 index 00000000..9d072035 --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterStateToConsole.hpp @@ -0,0 +1,30 @@ +/* @file KalmanFilterStateToConsole.hpp */ + +#pragma once + +#include "xo/reactor/Sink.hpp" +#include "KalmanFilterState.hpp" + +namespace xo { + namespace kalman { + class KalmanFilterStateToConsole + : public xo::reactor::SinkToConsole + { + public: + KalmanFilterStateToConsole() = default; + + static ref::rp make(); + + virtual void display(std::ostream & os) const; + //virtual std::string display_string() const; + }; /*KalmanFilterStateToConsole*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterStateToConsole const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace option*/ +} /*namespace xo*/ + +/* end KalmanFilterStateToConsole.hpp */ diff --git a/KalmanFilterStep.hpp b/include/xo/kalmanfilter/KalmanFilterStep.hpp similarity index 100% rename from KalmanFilterStep.hpp rename to include/xo/kalmanfilter/KalmanFilterStep.hpp diff --git a/include/xo/kalmanfilter/KalmanFilterSvc.hpp b/include/xo/kalmanfilter/KalmanFilterSvc.hpp new file mode 100644 index 00000000..94702a52 --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterSvc.hpp @@ -0,0 +1,64 @@ +/* @file KalmanFilterSvc.hpp */ + +#include "xo/reactor/Sink.hpp" +#include "xo/reactor/DirectSourcePtr.hpp" +#include "KalmanFilter.hpp" +#include "KalmanFilterInputSource.hpp" +#include "KalmanFilterOutputCallback.hpp" +#include "xo/callback/CallbackSet.hpp" + +namespace xo { + namespace kalman { + /* encapsulate a passive KalmanFilter + * instance as an active event consumer+producer + * + * sinks that want to consume KalmanFilterSvc events will use + * .attach_sink() (or .add_callback()) + */ + class KalmanFilterSvc : public xo::reactor::Sink1>, + public xo::reactor::DirectSourcePtr> { + public: + using AbstractSource = xo::reactor::AbstractSource; + + public: + /* named ctor idiom */ + static ref::rp make(KalmanFilterSpec spec); + + KalmanFilter const & filter() const { return filter_; } + + /* notify incoming observations; will trigger kalman filter step */ + void notify_ev(ref::rp const & input_kp1) override; + + // ----- inherited from reactor::AbstractSink ----- + + /* filter captures KF input pointer */ + virtual bool allow_volatile_source() const override { return false; } + virtual uint32_t n_in_ev() const override { return n_in_ev_; } + virtual void display(std::ostream & os) const override; + + // ----- inherited from reactor::AbstractSource ----- + + /* note: correct since KalmanFilterEngine.extrapolate() + * always creates new state object + */ + virtual bool is_volatile() const override { return false; } + + // ----- Inherited from AbstractEventProcessor ----- + + private: + KalmanFilterSvc(KalmanFilterSpec spec); + + private: + /* passive kalman filter */ + KalmanFilter filter_; + /* receive filter input from this source; see .attach_input() */ + ref::rp input_src_; + /* counts lifetime #of input events (see .notify_ev()) */ + uint32_t n_in_ev_ = 0; + /* publish filter state updates to these callbacks */ + fn::RpCallbackSet pub_; + }; /*KalmanFilterSvc*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* KalmanFilterSvc.hpp */ diff --git a/include/xo/kalmanfilter/KalmanFilterTransition.hpp b/include/xo/kalmanfilter/KalmanFilterTransition.hpp new file mode 100644 index 00000000..22d3de5b --- /dev/null +++ b/include/xo/kalmanfilter/KalmanFilterTransition.hpp @@ -0,0 +1,62 @@ +/* @file KalmanFilterTransition.hpp */ + +#pragma once + +//#include "time/Time.hpp" +#include +#include + +namespace xo { + namespace kalman { + + /* encapsulate transition behavior for a kalman filter + * before taking observations into account + */ + class KalmanFilterTransition { + public: + using MatrixXd = Eigen::MatrixXd; + using uint32_t = std::uint32_t; + + public: + KalmanFilterTransition() = default; + KalmanFilterTransition(MatrixXd F, + MatrixXd Q) + : F_{std::move(F)}, Q_{std::move(Q)} { assert(this->check_ok()); } + + static void reflect_self(); + + /* n: cardinality of state vector */ + uint32_t n_state() const; + + MatrixXd const & transition_mat() const { return F_; } + MatrixXd const & transition_cov() const { return Q_; } + + bool check_ok() const { + uint32_t n = F_.rows(); + bool f_is_nxn = ((F_.rows() == n) && (F_.cols() == n)); + bool q_is_nxn = ((Q_.rows() == n) && (Q_.cols() == n)); + + /* also would like to require: Q is +ve definite */ + + return f_is_nxn && q_is_nxn; + } /*check_ok*/ + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* [n x n] state transition matrix */ + MatrixXd F_; + /* [n x n] covariance matrix for system noise */ + MatrixXd Q_; + }; /*KalmanFilterTransition*/ + + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterTransition const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterTransition.hpp */ diff --git a/include/xo/kalmanfilter/init_filter.hpp b/include/xo/kalmanfilter/init_filter.hpp new file mode 100644 index 00000000..292a63ff --- /dev/null +++ b/include/xo/kalmanfilter/init_filter.hpp @@ -0,0 +1,20 @@ +/* file init_kalmanfilter.hpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#pragma once + +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + enum S_kalmanfilter_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + +/* end init_kalmanfilter.hpp */ diff --git a/include/xo/kalmanfilter/print_eigen.hpp b/include/xo/kalmanfilter/print_eigen.hpp new file mode 100644 index 00000000..ea6f7375 --- /dev/null +++ b/include/xo/kalmanfilter/print_eigen.hpp @@ -0,0 +1,41 @@ +/* @file print_eigen.hpp */ + +#include +#include + +namespace logutil { + template + class matrix { + public: + matrix(T x) : x_{std::move(x)} {} + + /* print this value */ + T x_; + }; /*matrix*/ + + template + using vector = matrix; + + template + inline std::ostream & + operator<<(std::ostream & s, matrix const & mat) + { + s << "["; + for(std::uint32_t i = 0, m = mat.x_.rows(); i 0) + s << "; "; + + for(std::uint32_t j = 0, n = mat.x_.cols(); j 0) + s << ' '; + + s << mat.x_(i, j); + } + } + s << "]"; + + return s; + } /*operator<<*/ +} /*namespace logutil*/ + +/* end print_eigen.hpp */ diff --git a/init_filter.cpp b/init_filter.cpp deleted file mode 100644 index 80407cc4..00000000 --- a/init_filter.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* file init_filter.cpp - * - * author: Roland Conybeare, Aug 2022 - */ - -#include "init_filter.hpp" -#include "reactor/init_reactor.hpp" -#include "KalmanFilterState.hpp" -#include "EigenUtil.hpp" -#include "printjson/PrintJson.hpp" - -namespace xo { - using xo::kalman::KalmanFilterInput; - using xo::kalman::KalmanFilterTransition; - using xo::kalman::KalmanFilterState; - using xo::kalman::KalmanFilterStateExt; - using xo::eigen::EigenUtil; - using xo::json::PrintJsonSingleton; - using xo::json::PrintJson; - - void - InitSubsys::init() - { - PrintJson * pjson = PrintJsonSingleton::instance().get(); - - EigenUtil::reflect_eigen(); - EigenUtil::provide_json_printers(pjson); - - KalmanFilterInput::reflect_self(); - KalmanFilterTransition::reflect_self(); - KalmanFilterState::reflect_self(); - KalmanFilterStateExt::reflect_self(); - - } /*init*/ - - InitEvidence - InitSubsys::require() - { - InitEvidence retval; - - /* subsystem dependencies for filter/ */ - retval ^= InitSubsys::require(); - - /* filter/'s own initialization code */ - retval ^= Subsystem::provide("filter", &init); - - return retval; - } /*require*/ -} /*namespace xo*/ - -/* end init_filter.cpp */ diff --git a/init_filter.hpp b/init_filter.hpp deleted file mode 100644 index 79cfc6d0..00000000 --- a/init_filter.hpp +++ /dev/null @@ -1,20 +0,0 @@ -/* file init_filter.hpp - * - * author: Roland Conybeare, Aug 2022 - */ - -#pragma once - -#include "subsys/Subsystem.hpp" - -namespace xo { - enum S_filter_tag {}; - - template<> - struct InitSubsys { - static void init(); - static InitEvidence require(); - }; -} /*namespace xo*/ - -/* end init_filter.hpp */ diff --git a/print_eigen.hpp b/print_eigen.hpp deleted file mode 100644 index 953c5904..00000000 --- a/print_eigen.hpp +++ /dev/null @@ -1,41 +0,0 @@ -/* @file print_eigen.hpp */ - -#include -#include - -namespace logutil { - template - class matrix { - public: - matrix(T x) : x_{std::move(x)} {} - - /* print this value */ - T x_; - }; /*matrix*/ - - template - using vector = matrix; - - template - inline std::ostream & - operator<<(std::ostream & s, matrix const & mat) - { - s << "["; - for(std::uint32_t i = 0, m = mat.x_.rows(); i 0) - s << "; "; - - for(std::uint32_t j = 0, n = mat.x_.cols(); j 0) - s << ' '; - - s << mat.x_(i, j); - } - } - s << "]"; - - return s; - } /*operator<<*/ -} /*namespace logutil*/ - -/* end print_eigen.hpp */ diff --git a/src/kalmanfilter/CMakeLists.txt b/src/kalmanfilter/CMakeLists.txt new file mode 100644 index 00000000..30ca8c02 --- /dev/null +++ b/src/kalmanfilter/CMakeLists.txt @@ -0,0 +1,31 @@ +# xo-kalmanfilter/src/kalmanfilter/CMakeLists.txt + +set(SELF_LIB xo_kalmanfilter) + +set(SELF_SRCS + EigenUtil.cpp + KalmanFilterInput.cpp + KalmanFilterInputToConsole.cpp + KalmanFilterState.cpp + KalmanFilterStateToConsole.cpp + KalmanFilterTransition.cpp + KalmanFilterObservable.cpp + KalmanFilterEngine.cpp + KalmanFilter.cpp + KalmanFilterStep.cpp + KalmanFilterSpec.cpp + KalmanFilterSvc.cpp + init_filter.cpp +) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# internal dependencies + +xo_dependency(${SELF_LIB} reactor) + +# ---------------------------------------------------------------- +# external dependencies + +xo_external_target_dependency(${SELF_LIB} eigen3 Eigen3::Eigen) diff --git a/src/kalmanfilter/EigenUtil.cpp b/src/kalmanfilter/EigenUtil.cpp new file mode 100644 index 00000000..b5770440 --- /dev/null +++ b/src/kalmanfilter/EigenUtil.cpp @@ -0,0 +1,157 @@ +/* file EigenUtil.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "EigenUtil.hpp" +#include "xo/printjson/PrintJson.hpp" +#include "xo/reflect/Reflect.hpp" +#include +#include +#include +#include + +namespace xo { + using xo::json::PrintJson; + using xo::json::JsonPrinter; + using xo::reflect::Reflect; + using xo::reflect::TypeDescr; + using VectorXb = Eigen::Array; + using Eigen::VectorXd; + using Eigen::MatrixXd; + +#ifdef NOT_YET + namespace reflect { + template + using EigenVectorX_Tdx = xo::reflect::StlVectorTdx>; + + /* probably need this to appear before decl for class xo::reflect::Reflect */ + template + class EstablishTdx> { + public: + static std::unique_ptr make() { + return EigenVectorX_Tdx::make(); + } /*make*/ + }; /*EstablishTdx*/ + } /*reflect*/ +#endif + + namespace eigen { + + namespace { + /* prints a VectorXd as json, in the obvious format, e.g. + * [1,2,3] + */ + template + class EigenVectorJsonPrinter : public JsonPrinter { + public: + EigenVectorJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override + { + EigenVectorType * pv = this->check_recover_native(tp, p_os); + + if (pv) { + /* EigenVectorType (VectorXb, VectorXd, ..) + * is reflected as atomic for now, out of expedience. + * + * as soon as we reflect as mt_vector, will not need this helper. + */ + *p_os << "["; + + for (std::uint32_t i = 0, n = pv->size(); i < n; ++i) { + if (i > 0) + *p_os << ","; + + /* note: need to dispatch via json printer for vector elements, + * to get special treatment for non-finite values + */ + this->pjson()->print((*pv)[i], p_os); + //*p_os << jsonp((*pv)[i], this->pjson()); + } + + *p_os << "]"; + } + } /*print_json*/ + }; /*EigenVectorJsonPrinter*/ + + /* prints a MatrixXd as json, in row-major format, e.g. + * [[1,2,3], [4,5,6], [7,8,9]] + */ + class MatrixXdJsonPrinter : public JsonPrinter { + public: + MatrixXdJsonPrinter(PrintJson const * pjson) : JsonPrinter(pjson) {} + + virtual void print_json(TaggedPtr tp, + std::ostream * p_os) const override + { + MatrixXd * pm = this->check_recover_native(tp, p_os); + + if (pm) { + /* MatrixXd is reflected as atomic for now, out of expedience */ + *p_os << "["; + + for(std::uint32_t i=0, m=pm->rows(); i 0) + *p_os << ", "; + *p_os << "["; + for(std::uint32_t j=0, n=pm->cols(); j 0) + *p_os << ","; + + /* note: need to dispatch via json printer for matrix elements, + * to get special treatment for non-finite values + */ + this->pjson()->print((*pm)(i, j), p_os); + //*p_os << jsonp((*pm)(i, j), this->pjson()); + } + *p_os << "]"; + } + + *p_os << "]"; + } + } /*print_json*/ + }; /*MatrixXdJsonPrinter*/ + + template + void + provide_eigen_vector_printer(PrintJson * p_pjson) + { + TypeDescr td = Reflect::require(); + std::unique_ptr pr(new EigenVectorJsonPrinter(p_pjson)); + + p_pjson->provide_printer(td, std::move(pr)); + } /*provide_eigen_vector_printer*/ + } /*namespace*/ + + void + EigenUtil::reflect_eigen() + { +#ifdef NOT_YET + Reflect::require(); + Reflect::require(); +#endif + } /*reflect_eigen*/ + + void + EigenUtil::provide_json_printers(PrintJson * p_pjson) + { + assert(p_pjson); + + provide_eigen_vector_printer(p_pjson); + provide_eigen_vector_printer(p_pjson); + + { + TypeDescr td = Reflect::require(); + std::unique_ptr pr(new MatrixXdJsonPrinter(p_pjson)); + + p_pjson->provide_printer(td, std::move(pr)); + } + } /*provide_json_printers*/ + } /*namespace eigen*/ +} /*namespace xo*/ + +/* end EigenUtil.cpp */ diff --git a/src/kalmanfilter/KalmanFilter.cpp b/src/kalmanfilter/KalmanFilter.cpp new file mode 100644 index 00000000..56507650 --- /dev/null +++ b/src/kalmanfilter/KalmanFilter.cpp @@ -0,0 +1,78 @@ +/* @file KalmanFilter.cpp */ + +#include "KalmanFilter.hpp" +#include "KalmanFilterEngine.hpp" +#include "print_eigen.hpp" +#include "xo/indentlog/scope.hpp" +#include "Eigen/src/Core/Matrix.h" + +namespace xo { + using xo::time::utc_nanos; + //using logutil::matrix; + using xo::scope; + using xo::tostr; + using xo::xtag; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + // ----- KalmanFilter ----- + + KalmanFilter::KalmanFilter(KalmanFilterSpec spec) + : filter_spec_{std::move(spec)}, + state_ext_{filter_spec_.start_ext()} + {} /*ctor*/ + + void + KalmanFilter::notify_input(ref::rp const & input_kp1) + { + scope log(XO_ENTER0(info)); + + /* on entry: + * .state_ext refers to t(k) + * on exit: + * .step refers to t(k+1) + * .state_ext refers to t(k+1) + */ + + log && log(xtag("step_dt", + input_kp1->tkp1() - this->state_ext_->tm())); + + /* establish step inputs for this filter step: + * F(k+1) (system transition matrix) + * Q(k+1) (system noise covariance matrix) + * H(k+1) (observation coupling matrix) + * R(k+1) (observation noise covariance matrix) + * z(k+1) (observation vector) + */ + this->step_ = this->filter_spec_.make_step(this->state_ext_, input_kp1); + + //if (lscope.enabled()) { lscope.log(xtag("step", this->step_)); } + + /* extrapolate filter state to t(k+1), + * and correct based on z(k+1) + */ + this->state_ext_ = KalmanFilterEngine::step(this->step_); + + //if (lscope.enabled()) { lscope.log(xtag("state_ext", this->state_ext_)); } + } /*notify_input*/ + + void + KalmanFilter::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilter::display_string() const + { + return tostr(*this); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilter.cpp */ diff --git a/src/kalmanfilter/KalmanFilterEngine.cpp b/src/kalmanfilter/KalmanFilterEngine.cpp new file mode 100644 index 00000000..e9533549 --- /dev/null +++ b/src/kalmanfilter/KalmanFilterEngine.cpp @@ -0,0 +1,360 @@ +/* @file KalmanFilterEngine.cpp + * + */ + +#include "KalmanFilterEngine.hpp" +#include "print_eigen.hpp" +#include "xo/indentlog/scope.hpp" +#include "Eigen/src/Core/Matrix.h" + +namespace xo { + using xo::time::utc_nanos; + using logutil::matrix; + using xo::scope; + using xo::xtag; + using Eigen::LDLT; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + // ----- KalmanFilterEngine ----- + + ref::rp + KalmanFilterEngine::extrapolate(utc_nanos tkp1, + ref::rp const & s, + KalmanFilterTransition const & f) + { + //constexpr char const * c_self_name + // = "KalmanFilterEngine::extrapolate"; + + /* prior estimates at t(k) */ + VectorXd const & x = s->state_v(); + MatrixXd const & P = s->state_cov(); + + /* model change from t(k) -> t(k+1) */ + MatrixXd const & F = f.transition_mat(); + MatrixXd const & Q = f.transition_cov(); + + if(F.cols() != x.rows()) { + scope log(XO_DEBUG(true /*debug_flag*/)); + + log("error: F*x: expected F.cols=x.rows", + xtag("F.cols", F.cols()), xtag("x.rows", x.rows())); + } + + /* x(k+1|k) */ + VectorXd x_ext = F * x; + + /* P(k+1|k) */ + MatrixXd P_ext = (F * P * F.transpose()) + Q; + + /* creating new state object here + * allows KalmanFilterSvc.is_volatile()=false + */ + + return KalmanFilterState::make(s->step_no() + 1, + tkp1, + std::move(x_ext), + std::move(P_ext), + f); + } /*extrapolate*/ + + VectorXd + KalmanFilterEngine::kalman_gain1(ref::rp const & skp1_ext, + KalmanFilterObservable const & h, + uint32_t j) + { + constexpr bool c_debug_enabled = false; + + scope log(XO_DEBUG(c_debug_enabled)); + + /* P(k+1|k) :: [n x n] */ + MatrixXd const & P_ext = skp1_ext->state_cov(); + + /* H(k) :: [m x n] */ + MatrixXd const & H = h.observable(); + /* R(k) :: [m x m] */ + MatrixXd const & R = h.observable_cov(); + + /* i'th col of H couples element #i of filter state to each member of input z(k); + * j'th row of H couples filter state to j'th observable + * + * Hj :: [1 x n] Hj is a row-vector + */ + auto Hj = H.row(j); + + /* Rjj is the j'th diagonal element of R */ + double Rjj = R(j, j); + + /* T + * M(k) = Hj * P(k+1|k) * Hj + Rjj + * + * M(k) is a [1 x 1] matrix + */ + double m = Hj * (P_ext * Hj.transpose()) + Rjj; + + /* -1 + * M(k) trivial, since M is [1 x 1] + */ + double m_inv = 1.0 / m; + + /* K :: [n x 1] */ + VectorXd K = P_ext * Hj.transpose() * m_inv; + + log && log("result", + xtag("P(k+1|k)", matrix(P_ext)), + xtag("R", matrix(R)), + xtag("m", m)); + + return K; + } /*kalman_gain1*/ + + MatrixXd + KalmanFilterEngine::kalman_gain(ref::rp const & skp1_ext, + KalmanFilterObservable const & h) + { + scope log(XO_DEBUG(false /*debug_enabled*/)); + + /* P(k+1|k) */ + MatrixXd const & P_ext = skp1_ext->state_cov(); + + MatrixXd const & H = h.observable(); + MatrixXd const & R = h.observable_cov(); + + uint32_t m = H.rows(); + uint32_t n = H.cols(); + + if ((P_ext.rows() != n) || (P_ext.cols() != n)) { + std::string err_msg + = tostr("kalman_gain: with dim(H) = [m x n] expect dim(P) = [n x n]", + xtag("m", m), xtag("n", n), + xtag("P.rows", P_ext.rows()), + xtag("P.cols", P_ext.cols())); + + throw std::runtime_error(err_msg); + } + + if ((R.rows() != m) || (R.cols() != m)) { + std::string err_msg + = tostr("kalman_gain: with dim(H) = [m x n] expect dim(R) = [m x m]", + xtag("m", m), xtag("n", n), + xtag("R.rows", R.rows()), xtag("R.cols", R.cols())); + + throw std::runtime_error(err_msg); + } + + /* kalman gain: + * T -1 + * K(k+1) = P(k+1|k).H(k) .M + * + * T / T \ -1 + * = P(k+1|k).H(k) .| H(k).P(k+1|k).H(k) + R(k) | + * \ / + * + * Notes: + * 1. the matrix M being inverted is symmetric, since represents covariances. + * 2. if diagonal of R(k) has no zeroes (i.e. all measurements are subject to error), + * then it must be non-negative definite + * 3. unless observation errors are perfectly correlated, M(k) + * is positive definite. + * 4. even though 3. holds, there may be a nearby non-positive-definite matrix M+dM, + * Factoring M with finite-precision arithmetic solves for M+dM instead of M; + * which may run into difficulty if M is only 'slighlty' +ve definite. + * If necessary add small diagonal correction D to M, + * sufficient to make M+D positive definite. + * This is equivalent to introducing additional + * uncorrelated observation error, so benign from a robustness perspective + * 5. In generally we usually want to avoid fully realizing a matrix inverse. + * In this case need to explicitly compute K as ingredient used to + * correct state covariance later. + * 6. However, if R is diagonal (which is in practice quite likely), + * then it's easy to decompose a suite of vector observations z(k+1) = [z1, ..zm]T + * into separate zi, with dt=0 separating them. + * Can use this to avoid computing the inverse. + * See .kalman_gain1(), .correct1() + * 7. .kalman_gain() works unaltered when H, R have been reindexed + * to exclude outliers/errors; this is true because .kalman_gain() does not + * use the observation vector z[], i.e. operates entirely in the reduced + * reindexed space. + */ + + MatrixXd M = H * P_ext * H.transpose() + R; + + /* will use to write M as: + * + * T T + * M = P .L.D.L .P + * + * where: + * P is a permutation matrix + * L is lower triangular, with unit diagonal + * D is diagonal + */ + LDLT ldlt = M.ldlt(); + + /* solve for the identity matrix to realize the inverse this way */ + MatrixXd I = MatrixXd::Identity(M.rows(), M.cols()); + + /* -1 + * M + */ + MatrixXd M_inv = ldlt.solve(I); + + /* K(k+1) */ + MatrixXd K = P_ext * H.transpose() * M_inv; + + log && log("result", + xtag("k", skp1_ext->step_no()), + xtag("P(k+1|k)", matrix(P_ext)), + xtag("H", matrix(H)), + xtag("R", matrix(R)), + xtag("M", matrix(M)), + xtag("K", matrix(K))); + + return K; + } /*kalman_gain*/ + + ref::rp + KalmanFilterEngine::correct1(ref::rp const & skp1_ext, + KalmanFilterObservable const & h, + ref::rp const & zkp1, + uint32_t j) + { + uint32_t n = skp1_ext->n_state(); + /* Kj :: [n x 1] */ + VectorXd Kj = kalman_gain1(skp1_ext, h, j); + /* H :: [m x n] */ + MatrixXd const & H = h.observable(); + VectorXd const & z = zkp1->z(); + + /* Hj :: [1 x n] the j'th row of H */ + auto const & Hj = H.row(j); + + + /* x(k+1|x) :: [n x 1] */ + VectorXd const & x_ext = skp1_ext->state_v(); + + /* P(k+1|k) :: [n x n] */ + MatrixXd const & P_ext = skp1_ext->state_cov(); + + /* innovj : difference between jth 'actual observation' + * and jth 'predicted observation' + */ + double innovj = z[j] - (Hj * x_ext); + + /* x(k+1) */ + VectorXd xkp1 = x_ext + (Kj * innovj); + + MatrixXd I = MatrixXd::Identity(n, n); + /* note: Kj [n x 1], Hj [1 x n], + * so Kj * Hj [n x n], with rank 1 + */ + MatrixXd Pkp1 = (I - (Kj * Hj)) * P_ext; + + return KalmanFilterStateExt::make(skp1_ext->step_no(), + skp1_ext->tm(), + xkp1, + Pkp1, + skp1_ext->transition(), + Kj, + j, + zkp1); + } /*correct1*/ + + ref::rp + KalmanFilterEngine::correct(ref::rp const & skp1_ext, + KalmanFilterObservable const & h, + ref::rp const & zkp1) + { + uint32_t n = skp1_ext->n_state(); + /* K :: [n x m] */ + MatrixXd K = kalman_gain(skp1_ext, h); + MatrixXd const & H = h.observable(); + /* z_orig[] is original observation vector before reindexing */ + VectorXd const & z_orig = zkp1->z(); + /* reindex z_orig, keeping only elements that appear in + */ + VectorXd z = z_orig(h.keep()); + + /* 'ext' short for 'extrapolated' */ + VectorXd const & x_ext = skp1_ext->state_v(); + MatrixXd const & P_ext = skp1_ext->state_cov(); + + /* innov: difference between 'actual observations' + * and 'predicted observations' + */ + VectorXd innov = z - (H * x_ext); + + /* x(k+1) :: [n x 1] */ + VectorXd xkp1 = x_ext + K * innov; + MatrixXd I = MatrixXd::Identity(n, n); + MatrixXd Pkp1 = (I - K * H) * P_ext; + + return KalmanFilterStateExt::make(skp1_ext->step_no(), + skp1_ext->tm(), + xkp1, + Pkp1, + skp1_ext->transition(), + K, + -1 /*j: not used*/, + zkp1); + } /*correct*/ + + ref::rp + KalmanFilterEngine::step(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1) + { + ref::rp skp1_ext + = KalmanFilterEngine::extrapolate(tkp1, sk, Fk); + + ref::rp skp1 + = KalmanFilterEngine::correct(skp1_ext, Hkp1, zkp1); + + return skp1; + } /*step*/ + + ref::rp + KalmanFilterEngine::step(KalmanFilterStep const & step_spec) + { + return step(step_spec.tkp1(), + step_spec.state(), + step_spec.model(), + step_spec.obs(), + step_spec.input()); + } /*step*/ + + ref::rp + KalmanFilterEngine::step1(utc_nanos tkp1, + ref::rp const & sk, + KalmanFilterTransition const & Fk, + KalmanFilterObservable const & Hkp1, + ref::rp const & zkp1, + uint32_t j) + { + ref::rp skp1_ext + = KalmanFilterEngine::extrapolate(tkp1, sk, Fk); + + ref::rp skp1 + = KalmanFilterEngine::correct1(skp1_ext, Hkp1, zkp1, j); + + return skp1; + } /*step1*/ + + ref::rp + KalmanFilterEngine::step1(KalmanFilterStep const & step_spec, + uint32_t j) + { + return step1(step_spec.tkp1(), + step_spec.state(), + step_spec.model(), + step_spec.obs(), + step_spec.input(), + j); + } /*step1*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterEngine.cpp */ diff --git a/src/kalmanfilter/KalmanFilterInput.cpp b/src/kalmanfilter/KalmanFilterInput.cpp new file mode 100644 index 00000000..96f0d6ca --- /dev/null +++ b/src/kalmanfilter/KalmanFilterInput.cpp @@ -0,0 +1,118 @@ +/* @file KalmanFilterInput.cpp */ + +#include "KalmanFilterInput.hpp" +#include "xo/reflect/StructReflector.hpp" +#include "Eigen/src/Core/Matrix.h" +#include "print_eigen.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/reflect/TaggedRcptr.hpp" + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedRcptr; + using xo::reflect::StructReflector; + using xo::scope; + using logutil::matrix; + using xo::xtag; + using Eigen::MatrixXd; + using Eigen::VectorXi; + using std::uint32_t; + + namespace kalman { + ref::rp + KalmanFilterInput::make(utc_nanos tkp1, + VectorXb const & presence, + VectorXd const & z, + VectorXd const & Rd) + { + return new KalmanFilterInput(tkp1, presence, z, Rd); + } /*make*/ + + ref::rp + KalmanFilterInput::make_present(utc_nanos tkp1, + VectorXd const & z) + { + VectorXb presence = VectorXb::Ones(z.cols()); + + return new KalmanFilterInput(tkp1, + presence, + z, + VectorXd(0) /*Rd - not using*/); + } /*make*/ + + VectorXi + KalmanFilterInput::make_kept_index() const + { + scope log(XO_DEBUG(false /*!debug_flag*/)); + + log && log(xtag("presence", matrix(presence_))); + + /* count truth values */ + uint32_t mstar = 0; + + for (uint32_t j = 0, m = this->presence_.rows(); jpresence_[j]) + ++mstar; + } + + log && log(xtag("m*", mstar)); + + VectorXi keep(mstar); + + /* 2nd pass, populate keep[] */ + + uint32_t jstar = 0; + + for (uint32_t j = 0, m = this->presence_.rows(); jpresence_[j]) { + keep[jstar] = j; + ++jstar; + } + } + + log && log("keep", matrix(keep)); + + return keep; + } /*make_kept_index*/ + + void + KalmanFilterInput::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, tkp1); + REFLECT_MEMBER(sr, presence); + REFLECT_MEMBER(sr, z); + REFLECT_MEMBER(sr, Rd); + } + } /*reflect_self*/ + + reflect::TaggedRcptr + KalmanFilterInput::self_tp() + { + return Reflect::make_rctp(this); + } /*self_tp*/ + + void + KalmanFilterInput::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterInput::display_string() const + { + std::stringstream ss; + this->display(ss); + return ss.str(); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterInput.cpp */ diff --git a/KalmanFilterInputToConsole.cpp b/src/kalmanfilter/KalmanFilterInputToConsole.cpp similarity index 93% rename from KalmanFilterInputToConsole.cpp rename to src/kalmanfilter/KalmanFilterInputToConsole.cpp index a0a848ca..8fadf031 100644 --- a/KalmanFilterInputToConsole.cpp +++ b/src/kalmanfilter/KalmanFilterInputToConsole.cpp @@ -1,7 +1,7 @@ /* @file KalmanFilterInputToConsole.cpp */ #include "KalmanFilterInputToConsole.hpp" -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/print/tag.hpp" namespace xo { using xo::xtag; diff --git a/KalmanFilterObservable.cpp b/src/kalmanfilter/KalmanFilterObservable.cpp similarity index 98% rename from KalmanFilterObservable.cpp rename to src/kalmanfilter/KalmanFilterObservable.cpp index 90f31460..ad90c1f1 100644 --- a/KalmanFilterObservable.cpp +++ b/src/kalmanfilter/KalmanFilterObservable.cpp @@ -2,7 +2,7 @@ #include "KalmanFilterObservable.hpp" #include "print_eigen.hpp" -#include "indentlog/scope.hpp" +#include "xo/indentlog/scope.hpp" namespace xo { using xo::scope; diff --git a/src/kalmanfilter/KalmanFilterSpec.cpp b/src/kalmanfilter/KalmanFilterSpec.cpp new file mode 100644 index 00000000..45185551 --- /dev/null +++ b/src/kalmanfilter/KalmanFilterSpec.cpp @@ -0,0 +1,27 @@ +/* @file KalmanFilterSpec.cpp */ + +#include "KalmanFilterSpec.hpp" +#include "xo/indentlog/scope.hpp" + +namespace xo { + using xo::tostr; + using xo::xtag; + + namespace kalman { + void + KalmanFilterSpec::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterSpec::display_string() const + { + return tostr(*this); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterSpec.cpp */ diff --git a/src/kalmanfilter/KalmanFilterState.cpp b/src/kalmanfilter/KalmanFilterState.cpp new file mode 100644 index 00000000..4b5679e3 --- /dev/null +++ b/src/kalmanfilter/KalmanFilterState.cpp @@ -0,0 +1,231 @@ +/* @file KalmanFilterState.cpp */ + +#include "KalmanFilterState.hpp" +#include "print_eigen.hpp" +#include "xo/reflect/StructReflector.hpp" +#include "xo/reflect/TaggedPtr.hpp" +#include "xo/indentlog/scope.hpp" +#include "Eigen/src/Core/Matrix.h" +#include +#include + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedRcptr; + using xo::reflect::StructReflector; + using xo::time::utc_nanos; + using xo::ref::rp; + using logutil::matrix; + using logutil::vector; + //using xo::scope; + using xo::xtag; + using xo::tostr; + //using Eigen::LDLT; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + // ----- KalmanFilterState ----- + + rp + KalmanFilterState::make() + { + return new KalmanFilterState(); + } /*make*/ + + rp + KalmanFilterState::make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition) + { + return new KalmanFilterState(k, tk, + std::move(x), + std::move(P), + std::move(transition)); + } /*make*/ + + void + KalmanFilterState::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, k); + REFLECT_MEMBER(sr, tk); + REFLECT_MEMBER(sr, x); + REFLECT_MEMBER(sr, P); + } + } /*reflect_self*/ + + KalmanFilterState::KalmanFilterState() = default; + + KalmanFilterState::KalmanFilterState(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition) + : k_{k}, tk_{tk}, + x_{std::move(x)}, P_{std::move(P)}, + transition_{std::move(transition)} + {} + + TaggedRcptr + KalmanFilterState::self_tp() + { + return Reflect::make_rctp(this); + } /*self_tp*/ + + // ----- KalmanFilterExt ----- + + rp + KalmanFilterStateExt::make() + { + return new KalmanFilterStateExt(); + } /*make*/ + + rp + KalmanFilterStateExt::make(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + rp zk) + { + return new KalmanFilterStateExt(k, + tk, + std::move(x), + std::move(P), + std::move(transition), + std::move(K), + j, + std::move(zk)); + } /*make*/ + + void + KalmanFilterStateExt::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + /* TODO: use sr.adopt_ancestors() */ + + REFLECT_EXPLICIT_MEMBER(sr, "k", &KalmanFilterState::k_); + REFLECT_EXPLICIT_MEMBER(sr, "tk", &KalmanFilterState::tk_); + REFLECT_EXPLICIT_MEMBER(sr, "x", &KalmanFilterState::x_); + REFLECT_EXPLICIT_MEMBER(sr, "P", &KalmanFilterState::P_); + REFLECT_EXPLICIT_MEMBER(sr, "transition", &KalmanFilterState::transition_); + REFLECT_MEMBER(sr, j); + REFLECT_MEMBER(sr, K); + REFLECT_MEMBER(sr, zk); + } + } /*reflect_self*/ + + KalmanFilterStateExt::KalmanFilterStateExt(uint32_t k, + utc_nanos tk, + VectorXd x, + MatrixXd P, + KalmanFilterTransition transition, + MatrixXd K, + int32_t j, + rp zk) + : KalmanFilterState(k, tk, + std::move(x), + std::move(P), + std::move(transition)), + j_{j}, + K_{std::move(K)}, + zk_{std::move(zk)} + { + uint32_t n = x.size(); + + if (n != P.rows() || n != P.cols()) { + std::string err_msg + = tostr("with n=x.size expect [n x n] covar matrix P", + xtag("n", x.size()), + xtag("P.rows", P.rows()), + xtag("P.cols", P.cols())); + + throw std::runtime_error(err_msg); + } + + if ((K.rows() > 0) && (K.rows() > 0)) { + if (n != K.rows()) { + std::string err_msg + = tostr("with n=x.size expect [m x n] gain matrix K", + xtag("n", x.size()), + xtag("K.rows", K.rows()), + xtag("K.cols", K.cols())); + + throw std::runtime_error(err_msg); + } + } else { + /* bypass test with [0 x 0] matrix K; + * normal for initial filter state + */ + } + } /*ctor*/ + + void + KalmanFilterState::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterState::display_string() const + { + std::stringstream ss; + ss << *this; + return ss.str(); + } /*display_string*/ + + // ----- KalmanFilterStateExt ----- + + ref::rp + KalmanFilterStateExt::initial(utc_nanos t0, + VectorXd x0, + MatrixXd P0) + { + return KalmanFilterStateExt::make + (0 /*k*/, + t0, + std::move(x0), + std::move(P0), + KalmanFilterTransition(MatrixXd() /*F - not used for initial step*/, + MatrixXd() /*Q - not used for initial step*/), + MatrixXd() /*K - not used for initial step*/, + -1 /*j - not used for initial step*/, + nullptr /*zk - not defined for initial step*/); + } /*initial*/ + + void + KalmanFilterStateExt::display(std::ostream & os) const + { + os << "step_no()) + << xtag("tk", this->tm()) + << xtag("x", matrix(this->state_v())) + << xtag("P", matrix(this->state_cov())) + << xtag("K", matrix(K_)) + << xtag("j", j_) + << ">"; + } /*display*/ + + TaggedRcptr + KalmanFilterStateExt::self_tp() + { + return Reflect::make_rctp(this); + } /*self_tp*/ + } /*namespace filter*/ +} /*namespace xo*/ + +/* end KalmanFilterState.cpp */ diff --git a/KalmanFilterStateToConsole.cpp b/src/kalmanfilter/KalmanFilterStateToConsole.cpp similarity index 93% rename from KalmanFilterStateToConsole.cpp rename to src/kalmanfilter/KalmanFilterStateToConsole.cpp index 8e76ce84..9559d339 100644 --- a/KalmanFilterStateToConsole.cpp +++ b/src/kalmanfilter/KalmanFilterStateToConsole.cpp @@ -1,7 +1,7 @@ /* @file KalmanFilterStateToConsole.cpp */ #include "KalmanFilterStateToConsole.hpp" -#include "indentlog/print/tag.hpp" +#include "xo/indentlog/print/tag.hpp" namespace xo { using xo::xtag; diff --git a/src/kalmanfilter/KalmanFilterStep.cpp b/src/kalmanfilter/KalmanFilterStep.cpp new file mode 100644 index 00000000..8ca90211 --- /dev/null +++ b/src/kalmanfilter/KalmanFilterStep.cpp @@ -0,0 +1,84 @@ +/* @file KalmanFilterStep.cpp */ + +#include "KalmanFilterStep.hpp" +#include "KalmanFilterEngine.hpp" +#include "KalmanFilterState.hpp" +#include "xo/indentlog/scope.hpp" + +namespace xo { + using xo::scope; + using xo::tostr; + using xo::xtag; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace kalman { + ref::rp + KalmanFilterStep::extrapolate() const + { + return KalmanFilterEngine::extrapolate(this->tkp1(), + this->state(), + this->model() /*transition*/); + } /*extrapolate*/ + + MatrixXd + KalmanFilterStep::gain(ref::rp const & skp1_ext) const + { + return KalmanFilterEngine::kalman_gain(skp1_ext, + this->obs()); + } /*gain*/ + + VectorXd + KalmanFilterStep::gain1(ref::rp const & skp1_ext, + uint32_t j) const + { + return KalmanFilterEngine::kalman_gain1(skp1_ext, + this->obs(), + j); + + } /*gain1*/ + + ref::rp + KalmanFilterStep::correct(ref::rp const & skp1_ext) + { + return KalmanFilterEngine::correct(skp1_ext, + this->obs(), + this->input()); + } /*correct*/ + + ref::rp + KalmanFilterStep::correct1(ref::rp const & skp1_ext, + uint32_t j) + { + return KalmanFilterEngine::correct1(skp1_ext, + this->obs(), + this->input(), + j); + } /*correct1*/ + + void + KalmanFilterStep::display(std::ostream & os) const + { + //scope lscope("KalmanFilterStep::display"); + + os << "model()); + //lscope.log("obs:"); + os << xtag("obs", this->obs()); + //lscope.log("input:"); + os << xtag("input", this->input()); + os << ">"; + } /*display*/ + + std::string + KalmanFilterStep::display_string() const + { + return tostr(*this); + } /*display_string*/ + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterStep.cpp */ diff --git a/KalmanFilterSvc.cpp b/src/kalmanfilter/KalmanFilterSvc.cpp similarity index 100% rename from KalmanFilterSvc.cpp rename to src/kalmanfilter/KalmanFilterSvc.cpp diff --git a/src/kalmanfilter/KalmanFilterTransition.cpp b/src/kalmanfilter/KalmanFilterTransition.cpp new file mode 100644 index 00000000..a6ec63ae --- /dev/null +++ b/src/kalmanfilter/KalmanFilterTransition.cpp @@ -0,0 +1,55 @@ +/* @file KalmanFilterTransition.cpp */ + +#include "KalmanFilterTransition.hpp" +#include "print_eigen.hpp" +#include "xo/reflect/StructReflector.hpp" +#include "xo/indentlog/scope.hpp" + +namespace xo { + using xo::reflect::StructReflector; + using logutil::matrix; + using xo::xtag; + + namespace kalman { + void + KalmanFilterTransition::reflect_self() + { + StructReflector sr; + + if (sr.is_incomplete()) { + REFLECT_MEMBER(sr, F); + REFLECT_MEMBER(sr, Q); + } + } /*reflect_self*/ + + uint32_t + KalmanFilterTransition::n_state() const + { + /* we know F.rows() == F.cols() = Q.cols() == Q.rows(), + * see .check_ok() + */ + + return F_.rows(); + } /*n_state*/ + + void + KalmanFilterTransition::display(std::ostream & os) const + { + os << ""; + } /*display*/ + + std::string + KalmanFilterTransition::display_string() const + { + std::stringstream ss; + this->display(ss); + return ss.str(); + } /*display_string*/ + + } /*namespace kalman*/ +} /*namespace xo*/ + +/* end KalmanFilterTransition.cpp */ diff --git a/src/kalmanfilter/init_filter.cpp b/src/kalmanfilter/init_filter.cpp new file mode 100644 index 00000000..3417214b --- /dev/null +++ b/src/kalmanfilter/init_filter.cpp @@ -0,0 +1,52 @@ +/* file init_filter.cpp + * + * author: Roland Conybeare, Aug 2022 + */ + +#include "init_filter.hpp" +#include "xo/reactor/init_reactor.hpp" + +#include "KalmanFilterState.hpp" +#include "EigenUtil.hpp" +#include "xo/printjson/PrintJson.hpp" + +namespace xo { + using xo::kalman::KalmanFilterInput; + using xo::kalman::KalmanFilterTransition; + using xo::kalman::KalmanFilterState; + using xo::kalman::KalmanFilterStateExt; + using xo::eigen::EigenUtil; + using xo::json::PrintJsonSingleton; + using xo::json::PrintJson; + + void + InitSubsys::init() + { + PrintJson * pjson = PrintJsonSingleton::instance().get(); + + EigenUtil::reflect_eigen(); + EigenUtil::provide_json_printers(pjson); + + KalmanFilterInput::reflect_self(); + KalmanFilterTransition::reflect_self(); + KalmanFilterState::reflect_self(); + KalmanFilterStateExt::reflect_self(); + + } /*init*/ + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* subsystem dependencies for filter/ */ + retval ^= InitSubsys::require(); + + /* filter/'s own initialization code */ + retval ^= Subsystem::provide("kalmanfilter", &init); + + return retval; + } /*require*/ +} /*namespace xo*/ + +/* end init_filter.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..9f21bfa4 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,54 @@ +# xo-kalmanfilter/utest/CMakeLists.txt + +set(SELF_EXE utest.filter) + +set(SELF_SRCS + KalmanFilter.test.cpp + filter_utest_main.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +target_code_coverage(${SELF_EXE} AUTO ALL) + +# ---------------------------------------------------------------- +# create convenience symlink to canned data + +#create_symlink("${CMAKE_SOURCE_DIR}/utestdata/" "src/filter/utest/utestdata") + +# ---------------------------------------------------------------- +# generic project dependency + +# PROJECT_SOURCE_DIR: +# so we can for example write +# #include "logutil/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 +# +xo_self_dependency(${SELF_EXE} xo_kalmanfilter) + +# ---------------------------------------------------------------- +# internal dependencies: logutil, ... + +xo_dependency(${SELF_EXE} xo_statistics) + +# ---------------------------------------------------------------- +# external dependencies: catch2.. + +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# ---------------------------------------------------------------- +# make standard directories for std:: includes explicit +# so that +# (1) they appear in compile_commands.json. +# (2) clangd (run from emacs lsp-mode) can find them +# +if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +endif() + +# end CMakeLists.txt diff --git a/utest/KalmanFilter.test.cpp b/utest/KalmanFilter.test.cpp new file mode 100644 index 00000000..679f461f --- /dev/null +++ b/utest/KalmanFilter.test.cpp @@ -0,0 +1,690 @@ +/* @file KalmanFilter.test.cpp */ + +#include "xo/kalmanfilter/KalmanFilter.hpp" +#include "xo/kalmanfilter/KalmanFilterEngine.hpp" +#include "xo/kalmanfilter/print_eigen.hpp" +#include "statistics/SampleStatistics.hpp" +#include "xo/randomgen/normalgen.hpp" +#include "xo/randomgen/xoshiro256.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/log_level.hpp" +#include +#include + +namespace xo { + using xo::kalman::KalmanFilterSpec; + using xo::kalman::KalmanFilterStep; + using xo::kalman::KalmanFilterEngine; + using xo::kalman::KalmanFilterStateExt; + using xo::kalman::KalmanFilterState; + using xo::kalman::KalmanFilterTransition; + using xo::kalman::KalmanFilterObservable; + using xo::kalman::KalmanFilterInput; + using xo::statistics::SampleStatistics; + using xo::rng::normalgen; + using xo::rng::xoshiro256ss; + using xo::time::timeutil; + using xo::time::utc_nanos; + using xo::time::seconds; + using xo::ref::rp; + using xo::log_level; + using logutil::matrix; + //using logutil::scope; + //using logutil::tostr; + //using logutil::xtag; + using xo::print::ccs; + using Eigen::MatrixXd; + using Eigen::VectorXd; + + namespace ut { + namespace { + /* step for kalman filter with: + * - single state variable x[0] + * - identity process model - x(k+1) = F(k).x(k), with F(k) = | 1 | + * - no process noise + * - single observation z[0] + * - identity coupling matrix - z(k) = H(k).x(k) + w(k), with H(k) = | 1 | + */ + KalmanFilterSpec::MkStepFn + kalman_identity1_mkstep_fn() + { + /* kalman state transition matrix: use identity <--> state is constant */ + MatrixXd F = MatrixXd::Identity(1, 1); + + /* state transition noise: set this to zero; + * measuring something that's known to be constant + */ + MatrixXd Q = MatrixXd::Zero(1, 1); + + /* single direct observation */ + MatrixXd H = MatrixXd::Identity(1, 1); + + /* observation errors understood to have + * mean 0, sdev 1 + * + * This is consistent with normal_rng below, + * so R is correctly specified + */ + MatrixXd R = MatrixXd::Identity(1, 1); + + return [F, Q, H, R](rp const & sk, + rp const & zkp1) { + KalmanFilterTransition Fk(F, Q); + KalmanFilterObservable Hk = KalmanFilterObservable::keep_all(H, R); + + return KalmanFilterStep(sk, Fk, Hk, zkp1); + }; + } /*kalman_identity1_mkstep_fn*/ + } /*namespace*/ + + /* example 1. + * repeated direct observation of a scalar + * use rng to generate observations + */ + TEST_CASE("kalman-identity", "[kalmanfilter]") { + /* setting up trivial filter for repeated indept + * measurements of a constant. + * + * True value of unknown set to 10, + * utest observes filter converging toward that value + */ + + /* seed for rng */ + uint64_t seed = 14950319842636922572UL; + + /* N(0,1) random numbers */ + auto normal_rng + = (normalgen::make + (seed, + std::normal_distribution(0.0 /*mean*/, + 1.0 /*sdev*/))); + + /* accumulate statistics on 'measurements', + * use as reference implementation for filter + */ + SampleStatistics z_stats; + + utc_nanos t0 = timeutil::ymd_midnight(20220707); + + /* estimate x(0) = [0] */ + VectorXd x0(1); + x0 << 10.0 + normal_rng(); + + INFO(tostr("x0=", x0)); + + z_stats.include_sample(x0[0]); + + /* kalman prior : Variance = 1, sdev = 1 */ + MatrixXd P0 = 1.0 * MatrixXd::Identity(1, 1); + + /* F, Q, K, j, zk not used for initial state */ + rp s0 + = KalmanFilterStateExt::make(0 /*step#*/, + t0, + x0, + P0, + KalmanFilterTransition(MatrixXd::Zero(1, 1) /*F*/, + MatrixXd::Zero(1, 1) /*Q*/), + MatrixXd::Zero(1, 1) /*K*/, + -1 /*j*/, + nullptr /*zk*/); + + auto mk_step_fn + = kalman_identity1_mkstep_fn(); + + KalmanFilterSpec spec(s0, mk_step_fn); + + rp sk = spec.start_ext(); + + for(uint32_t i_step = 1; i_step < 100; ++i_step) { + /* note: for this filter, measurement time doesn't matter */ + utc_nanos tkp1 = sk->tm() + seconds(1); + + VectorXd z(1); + z << 10.0 + normal_rng(); + + INFO(tostr("z=", z)); + + z_stats.include_sample(z[0]); + + ref::rp inputk + = KalmanFilterInput::make_present(tkp1, z); + + KalmanFilterStep step_spec = spec.make_step(sk, inputk); + + rp skp1 + = KalmanFilterEngine::step(step_spec); + + REQUIRE(skp1->step_no() == i_step); + REQUIRE(skp1->tm() == tkp1); + REQUIRE(skp1->n_state() == 1); + REQUIRE(skp1->state_v().size() == 1); + REQUIRE(skp1->state_v()[0] == Approx(z_stats.mean()).epsilon(1e-6)); + REQUIRE(skp1->state_cov().rows() == 1); + REQUIRE(skp1->state_cov().cols() == 1); + REQUIRE(skp1->gain().rows() == 1); + REQUIRE(skp1->gain().cols() == 1); + REQUIRE(skp1->observable() == -1); + + /* z_stats reflects k = z_stats.n_sample() N(0,1) 'random' vars; + * variance of sum (i.e. z_stats.mean() * k) is proportional to k; + * variance of mean like 1/k + * + * kalman filter also should compute covariance estimate like 1/k + */ + + REQUIRE(skp1->state_cov()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-6)); + + REQUIRE(skp1->gain()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-6)); + + /* estimate at each step should be (approximately) + * average of measurements taken so far. + * approximate because also affected by prior + */ + + sk = skp1; + } + + REQUIRE(sk->state_v()[0] == Approx(10.0).epsilon(1e-2)); + REQUIRE(sk->state_cov()(0, 0) == Approx(0.01).epsilon(1e-6)); + REQUIRE(sk->gain()(0, 0) == Approx(0.01).epsilon(1e-6)); + } /*TEST_CASE(kalman-identity)*/ + + /* example 2. + * like example 1, but using "separate observation" variants: + * KalmanGain::correct1() // per observation + * instead of + * KalmanGain::correct() // per observation set + */ + TEST_CASE("kalman-identity1", "[kalmanfilter]") { + /* setting up trivial filter for repeated indept + * measurements of a constant. + * + * True value of unknown set to 10, + * utest observes filter converging toward that value + * + */ + + /* seed for rng */ + uint64_t seed = 14950319842636922572UL; + + /* N(0,1) random numbers */ + auto normal_rng + = (normalgen::make + (seed, + std::normal_distribution(0.0 /*mean*/, + 1.0 /*sdev*/))); + + /* accumulate statistics on 'measurements', + * use as reference implementation for filter + */ + SampleStatistics z_stats; + + utc_nanos t0 = timeutil::ymd_midnight(20220707); + + /* estimate x(0) = [0] */ + VectorXd x0(1); + x0 << 10.0 + normal_rng(); + + INFO(tostr("x0=", x0)); + + z_stats.include_sample(x0[0]); + + /* kalman prior : Variance = 1, sdev = 1 */ + MatrixXd P0 = 1.0 * MatrixXd::Identity(1, 1); + + rp s0 + = KalmanFilterStateExt::make(0 /*step#*/, + t0, + x0, + P0, + KalmanFilterTransition(MatrixXd::Zero(1, 1) /*F*/, + MatrixXd::Zero(1, 1) /*Q*/), + MatrixXd::Zero(1, 1) /*K*/, + -1, + nullptr /*zk*/); + + auto mk_step_fn + = kalman_identity1_mkstep_fn(); + + KalmanFilterSpec spec(s0, mk_step_fn); + + rp sk = spec.start_ext(); + + for(uint32_t i_step = 1; i_step < 100; ++i_step) { + /* note: for this filter, measurement time doesn't matter */ + utc_nanos tkp1 = sk->tm() + seconds(1); + + VectorXd z(1); + z << 10.0 + normal_rng(); + + INFO(tostr("z=", z)); + + z_stats.include_sample(z[0]); + + ref::rp inputk + = KalmanFilterInput::make_present(tkp1, z); + + KalmanFilterStep step_spec + = spec.make_step(sk, inputk); + + rp skp1 + = KalmanFilterEngine::step1(step_spec, 0 /*j*/); + + REQUIRE(skp1->step_no() == i_step); + REQUIRE(skp1->tm() == tkp1); + REQUIRE(skp1->n_state() == 1); + REQUIRE(skp1->state_v().size() == 1); + REQUIRE(skp1->state_v()[0] == Approx(z_stats.mean()).epsilon(1e-6)); + REQUIRE(skp1->state_cov().rows() == 1); + REQUIRE(skp1->state_cov().cols() == 1); + REQUIRE(skp1->gain().rows() == 1); + REQUIRE(skp1->gain().cols() == 1); + REQUIRE(skp1->observable() == 0); + + /* z_stats reflects k = z_stats.n_sample() N(0,1) 'random' vars; + * variance of sum (i.e. z_stats.mean() * k) is proportional to k; + * variance of mean like 1/k + * + * kalman filter also should compute covariance estimate like 1/k + */ + + REQUIRE(skp1->state_cov()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-6)); + + REQUIRE(skp1->gain()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-6)); + + /* estimate at each step should be (approximately) + * average of measurements taken so far. + * approximate because also affected by prior + */ + + sk = skp1; + } + + REQUIRE(sk->state_v()[0] == Approx(10.0).epsilon(1e-2)); + REQUIRE(sk->state_cov()(0, 0) == Approx(0.01).epsilon(1e-6)); + REQUIRE(sk->gain()(0, 0) == Approx(0.01).epsilon(1e-6)); + } /*TEST_CASE(kalman-identity1)*/ + + namespace { + /* step for kalman filter with: + * - single state variable x[0] + * - identity process model: x(k+1) = F(k).x(k), with F(k) = | 1 | + * - no process noise + * - two observations z[0], z[1] + * - identity coupling matrix: z(k) = H(k).x(k) + w(k), with + * H(k) = | 1 | + * | 1 | + * + * w(k) = | w1 | with w1 ~ N(0,1) + * | w2 | + */ + KalmanFilterSpec::MkStepFn + kalman_identity2_mkstep_fn() + { + /* kalman state transition matrix: use identity <-> state is constant */ + MatrixXd F = MatrixXd::Identity(1, 1); + + /* state transition noise: set to 0 */ + MatrixXd Q = MatrixXd::Zero(1, 1); + + /* two direct observations */ + MatrixXd H = MatrixXd::Constant(2 /*#rows*/, 1 /*#cols*/, 1.0 /*M(i,j)*/); + + /* observation errors: N(0,1) */ + MatrixXd R = MatrixXd::Identity(2, 2); + + return [F, Q, H, R](rp const & sk, + rp const & zkp1) { + KalmanFilterTransition Fk(F, Q); + KalmanFilterObservable Hk = KalmanFilterObservable::keep_all(H, R); + + return KalmanFilterStep(sk, Fk, Hk, zkp1); + }; + } /*kalman_identity2_mkstep_fn*/ + } /*namespace*/ + + TEST_CASE("kalman-identity2", "[kalmanfilter]") { + /* variation on filter in kalman-identity1 utest above; + * this time make 2 observations per step + */ + + /* seed for rng */ + uint64_t seed = 14950319842636922572UL; + + /* N(0,1) random numbers */ + auto normal_rng + = (normalgen::make + (seed, + std::normal_distribution(0.0 /*mean*/, + 1.0 /*sdev*/))); + + /* accumulate statistics on 'measurements', + * use as reference implementation for filter + */ + SampleStatistics z_stats; + + utc_nanos t0 = timeutil::ymd_midnight(20220707); + + /* estimate x(0) = [0] */ + VectorXd x0(1); + x0 << 10.0 + normal_rng(); + + INFO(tostr("x0=", x0)); + + z_stats.include_sample(x0[0]); + + /* kalman prior : Variance = 1, sdev = 1 */ + MatrixXd P0 = 1.0 * MatrixXd::Identity(1, 1); + + rp s0 + = KalmanFilterStateExt::make(0 /*step#*/, + t0, + x0, + P0, + KalmanFilterTransition(MatrixXd::Zero(1, 1) /*F*/, + MatrixXd::Zero(1, 1) /*Q*/), + MatrixXd::Zero(1, 1) /*K*/, + -1 /*j*/, + nullptr /*zk*/); + + auto mk_step_fn + = kalman_identity2_mkstep_fn(); + + KalmanFilterSpec spec(s0, mk_step_fn); + rp sk = spec.start_ext(); + + /* need 1/2 as many filter steps to reach same confidence + * as in test "kalman-identity" + */ + for(uint32_t i_step = 1; i_step < 51; ++i_step) { + INFO(tostr(xtag("i_step", i_step))); + + /* note: for this filter, measurement time doesn't affect behavior */ + utc_nanos tkp1 = sk->tm() + seconds(1); + + VectorXd z(2); + z << 10.0 + normal_rng(), 10.0 + normal_rng(); + + z_stats.include_sample(z[0]); + z_stats.include_sample(z[1]); + + INFO(tostr(xtag("i_step", i_step), xtag("z", z))); + + ref::rp inputk + = KalmanFilterInput::make_present(tkp1, z); + + KalmanFilterStep step_spec = spec.make_step(sk, inputk); + + rp skp1 = KalmanFilterEngine::step(step_spec); + + REQUIRE(skp1->step_no() == i_step); + REQUIRE(skp1->tm() == tkp1); + REQUIRE(skp1->n_state() == 1); + REQUIRE(skp1->state_v().size() == 1); + REQUIRE(skp1->state_v()[0] == Approx(z_stats.mean()).epsilon(1e-6)); + REQUIRE(skp1->state_cov().rows() == 1); + REQUIRE(skp1->state_cov().cols() == 1); + REQUIRE(skp1->gain().rows() == 1); + REQUIRE(skp1->gain().cols() == 2); + REQUIRE(skp1->observable() == -1); + /* z_stats reflects 2*k = z_stats.n_sample() N(0,1) 'random' vars + * (since 2 vars per step) + * variance of sum (i.e. z_stats.mean() * k) is proportional to k; + * variance of mean like 1/k + * + * kalman filter also should compute covariance estimate like 1/k + * + */ + + REQUIRE(skp1->state_cov()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-6)); + REQUIRE(skp1->gain()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-6)); + REQUIRE(skp1->gain()(0, 1) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-6)); + + /* estimate at each step should be (approximately) + * average of measurements taken so far. + * approximate because also affected by prior + */ + + sk = skp1; + } + + REQUIRE(sk->state_v()[0] == Approx(10.0).epsilon(1e-2)); + REQUIRE(sk->state_cov()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-3)); + /* result is close but not identical, + * because initial confidence P0 counts as one sample, + * so have odd #of samples + */ + REQUIRE(sk->gain()(0, 0) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-3)); + REQUIRE(sk->gain()(0, 1) == Approx(1.0 / z_stats.n_sample()).epsilon(1e-3)); + } /*TEST_CASE(kalman-identity2)*/ + + namespace { + /* step for kalman filter with: + * - two state variables x[0], x[1] + * x[0] subject to random disturbances, reverts towards mean 1 + * x[1] is 1 + * - process model: x(k+1) = F(k).x(k) + v(k) with + * F(k) = | 0.95 0.05 | v(k) = | v1 |, v ~ N(0, 0.25) + * | 0 1 | | 0 | + * - one observation z[0] + * - coupling matrix: z(k) = H(k).x(k) + w(k), with + * H(k) = | 1 0 | + * + * w(k) ~ N(0,1) + */ + KalmanFilterSpec::MkStepFn + kalman_revert1_mkstep_fn() + { + /* kalman state transition matrix */ + MatrixXd F(2,2); + F << 0.95, 0.05, 0, 1; + + /* state transition noise */ + MatrixXd Q(2,2); + Q << 0.0001, 0.0, 0.0, 0.0; + + /* coupling matrix */ + MatrixXd H(1,2); + H << 1.0, 0.0; + + /* observation errors */ + MatrixXd R(1,1); + R << 0.25; + + return [F, Q, H, R](rp const & sk, + rp const & zkp1) { + KalmanFilterTransition Fk(F, Q); + KalmanFilterObservable Hk = KalmanFilterObservable::keep_all(H, R); + + return KalmanFilterStep(sk, Fk, Hk, zkp1); + }; + } /*kalman_revert1_mkstep_fn*/ + } /*namespace*/ + + TEST_CASE("kalman-revert1", "[kalmanfilter]") { + /* like test "kalman-identity", + * but introduce some system noise. + */ + + constexpr bool c_debug_enabled = false; + scope lscope(XO_DEBUG2(c_debug_enabled, "TEST(kalman_revert)")); + + /* seed for rng */ + uint64_t seed = 14950139742636922572UL; + + /* N(0,1) random numbers */ + auto normal_rng + = (normalgen::make + (seed, + std::normal_distribution(0.0 /*mean*/, + 1.0 /*sdev*/))); + + /* accumulate statistics on observations, + * use as reference when assessing filter behavior + */ + SampleStatistics z_stats; + + /* write output to file - use as baseline for regression testing */ + std::string self_test_name = Catch::getResultCapture().getCurrentTestName(); + + /* write space-delimited output, suitable for gnuplot + * omit always-constant values, rely on unit test verifying these + */ + std::ofstream out(self_test_name); + out << "step z0 x0 P00 K0" << std::endl; + + utc_nanos t0 = timeutil::ymd_midnight(20220707); + + /* estimate x(0). + * x(0)[1] is constant 1, used to achieve mean reversion + */ + VectorXd x0(2); + x0 << 1.0 + normal_rng(), 1.0; + + z_stats.include_sample(x0[0]); + + /* kalman prior : Variance = 1, sdev = 1 */ + MatrixXd P0(2,2); + P0 << 1.0, 0.0, 0.0, 0.0; + + rp s0 + = KalmanFilterStateExt::make(0 /*step#*/, + t0, + x0, + P0, + KalmanFilterTransition(MatrixXd::Zero(1, 1) /*F*/, + MatrixXd::Zero(1, 1) /*Q*/), + MatrixXd::Zero(1, 1) /*K*/, + -1 /*j*/, + nullptr /*zk*/); + + auto mk_step_fn + = kalman_revert1_mkstep_fn(); + + KalmanFilterSpec spec(s0, mk_step_fn); + rp sk = spec.start_ext(); + + for(uint32_t i_step = 1; i_step < 100; ++i_step) { + /* note: for this filter, measurement time doesn't affect behavior */ + utc_nanos tkp1 = sk->tm() + seconds(1); + + VectorXd z(1); + z << 1.0 + normal_rng(); + + z_stats.include_sample(z[0]); + + ref::rp inputk + = KalmanFilterInput::make_present(tkp1, z); + KalmanFilterStep step_spec = spec.make_step(sk, inputk); + rp skp1 = KalmanFilterEngine::step(step_spec); + + if (c_debug_enabled) { + lscope.log("filter", + xtag("step", i_step), + xtag("z", matrix(z)), + xtag("x", matrix(skp1->state_v())), + xtag("P", matrix(skp1->state_cov())), + xtag("K", matrix(skp1->gain()))); + } + + /* headings: step z0 x0 P00 K0 */ + out << i_step + << " " << z(0) + << " " << skp1->state_v()(0) + << " " << skp1->state_cov()(0, 0) + << " " << skp1->gain()(0, 0) + << "\n"; + + REQUIRE(skp1->step_no() == i_step); + REQUIRE(skp1->tm() == tkp1); + REQUIRE(skp1->n_state() == 2); + // + REQUIRE(skp1->state_v().size() == 2); + REQUIRE(skp1->state_v()(1) == 1.0); + // + REQUIRE(skp1->state_cov().rows() == 2); + REQUIRE(skp1->state_cov().cols() == 2); + // test skp1->state_cov()(0,0) vs baseline + REQUIRE(skp1->state_cov()(0, 0) >= 0.0); + REQUIRE(skp1->state_cov()(1, 0) == 0.0); + REQUIRE(skp1->state_cov()(0, 1) == 0.0); + REQUIRE(skp1->state_cov()(1, 1) == 0.0); + // + REQUIRE(skp1->gain().rows() == 2); + REQUIRE(skp1->gain().cols() == 1); + REQUIRE(skp1->gain()(0, 0) > 0.0); + REQUIRE(skp1->gain()(1, 0) == 0.0); + // + REQUIRE(skp1->observable() == -1); + // + + sk = skp1; + } + + out << std::flush; + out.close(); + + /* compare output with regression baseline. + * command like: + * diff kalman-revert1 utestdata/filter/kalman-revert1 + */ + char cmd_buf[256]; + snprintf(cmd_buf, sizeof(cmd_buf), + "diff %s utestdata/filter/%s\n", + self_test_name.c_str(), + self_test_name.c_str()); + + INFO(tostr(self_test_name, xtag("cmd", ccs(cmd_buf)))); + + std::int32_t err = ::system(cmd_buf); + + REQUIRE(err == 0); + } /*TEST_CASE(kalman-drift)*/ + +#ifdef NOT_IN_USE + namespace { + /* step for kalman filter with: + * - two state variables x[0], x[1] + * - identity process model: x(k+1) = F(k).x(k), with + * F(k) = | 1 0 | + * | 0 1 | + * - no process noise + * - two observations z[0], z[1] + * - simple coupling matrix: z(k) = H(k).x(k) + w(k), with + * H(k) = | 1 0 | + * | 0 -1 | + * (so sign of z[1] is reversed w.r.t x[1]) + * + * w(k) = | w1 | with w1 ~ N(0,1) + * | w2 | + */ + KalmanFilterSpec::MkStepFn + kalman_identity2x2_mkstep_fn() + { + /* kalman state transition matrix: use identity <-> state is constant */ + MatrixXd F = MatrixXd::Identity(2, 2); + + /* state transition noise: set to 0 */ + MatrixXd Q = MatrixXd::Zero(2, 2); + + /* two direct observations */ + MatrixXd H = MatrixXd::Constant(2 /*#rows*/, 1 /*#cols*/, 1.0 /*M(i,j)*/); + + /* observation errors: N(0,1) */ + MatrixXd R = MatrixXd::Identity(2, 2); + + return [F, Q, H, R](KalmanFilterState const & sk, + KalmanFilterInput const & zkp1) { + KalmanFilterTransition Fk(F, Q); + KalmanFilterObservable Hk(H, R); + + return KalmanFilterStep(sk, Fk, Hk, zkp1); + }; + } /*kalman_identity2_mkstep_fn*/ + } /*namespace*/ +#endif + } /*namespace ut*/ +} /*namespace xo*/ + +/* end KalmanFilter.test.cpp */ diff --git a/utest/filter_utest_main.cpp b/utest/filter_utest_main.cpp new file mode 100644 index 00000000..9ad0266a --- /dev/null +++ b/utest/filter_utest_main.cpp @@ -0,0 +1,6 @@ +/* @file filter_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end filter_utest_main.cpp */ From 1bd545590afccc62070e4c7603fe1a37b6288db0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 12:01:29 -0400 Subject: [PATCH 0386/2524] bugfix: include paths --- include/xo/reactor/DirectSourcePtr.hpp | 28 +-- include/xo/reactor/LastReducer.hpp | 248 ++++++++++++------------- include/xo/reactor/SecondarySource.hpp | 3 +- 3 files changed, 139 insertions(+), 140 deletions(-) diff --git a/include/xo/reactor/DirectSourcePtr.hpp b/include/xo/reactor/DirectSourcePtr.hpp index db9c7734..8947a74a 100644 --- a/include/xo/reactor/DirectSourcePtr.hpp +++ b/include/xo/reactor/DirectSourcePtr.hpp @@ -2,24 +2,24 @@ #pragma once -#include "reactor/SecondarySource.hpp" -#include "reactor/LastReducer.hpp" -#include "reactor/EventTimeFn.hpp" +#include "SecondarySource.hpp" +#include "LastReducer.hpp" +#include "EventTimeFn.hpp" namespace xo { - namespace reactor { - template - using DirectSource = SecondarySource>>; + namespace reactor { + template + using DirectSource = SecondarySource>>; - /* use when Event is ref::rp for some T */ - template - using DirectSourcePtr = SecondarySource>>; + /* use when Event is ref::rp for some T */ + template + using DirectSourcePtr = SecondarySource>>; - } /*namespace reactor*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end DirectSourcePtr.hpp */ diff --git a/include/xo/reactor/LastReducer.hpp b/include/xo/reactor/LastReducer.hpp index 86a5913c..b18f1a09 100644 --- a/include/xo/reactor/LastReducer.hpp +++ b/include/xo/reactor/LastReducer.hpp @@ -2,153 +2,153 @@ #pragma once -#include "reactor/Reducer.hpp" +#include "Reducer.hpp" #include namespace xo { - namespace reactor { - /* implementation record used in LastReducer. - * LastReducer (see below) remembers a single event, - * + will be updated on successive calls to - * LastReducer.include_event() - * - * need to remember the _first_ (& therefore earliest) - * event timestamp in such a wave, since that establishes when simulator - * should deliver the event -- even if event is subsequently - * overwritten. - * - * once event is delivered, timestamp can reset - * - * otherwise if upstream producer sends events with - * future timestamps, can get indefinite postponement - * with simulation clock failing to catch up to event time. - * - */ - - template - class EventRecd { - public: - using utc_nanos = xo::time::utc_nanos; + namespace reactor { + /* implementation record used in LastReducer. + * LastReducer (see below) remembers a single event, + * + will be updated on successive calls to + * LastReducer.include_event() + * + * need to remember the _first_ (& therefore earliest) + * event timestamp in such a wave, since that establishes when simulator + * should deliver the event -- even if event is subsequently + * overwritten. + * + * once event is delivered, timestamp can reset + * + * otherwise if upstream producer sends events with + * future timestamps, can get indefinite postponement + * with simulation clock failing to catch up to event time. + * + */ - public: - EventRecd() = default; - EventRecd(utc_nanos tm, Event ev) : trigger_tm_{tm}, ev_{ev} {} - EventRecd(utc_nanos tm, Event && ev) : trigger_tm_{tm}, ev_{std::move(ev)} {} + template + class EventRecd { + public: + using utc_nanos = xo::time::utc_nanos; - public: - /* if sim, deliver event when simulation clock reaches - * .trigger_tm; .trigger_tm can be earlier than .ev time - */ - utc_nanos trigger_tm_; - /* event to deliver */ - Event ev_; - }; + public: + EventRecd() = default; + EventRecd(utc_nanos tm, Event ev) : trigger_tm_{tm}, ev_{ev} {} + EventRecd(utc_nanos tm, Event && ev) : trigger_tm_{tm}, ev_{std::move(ev)} {} - /* reducer that just remembers the last event - * - * Require: - * - Event is null-contructible - * - Event is copyable - * - * LastReducer provides reentrancy support. This support doesn't operate - * if Event copy is not deep, e.g. for Event = rpn - * - * .include_event() - * /-------\ -----------------> /------\ - * | empty | | full | - * \-------/ <----------------- \------/ - * . .annex_one() . - * . . - * .is_empty()=true .is_empty()=false - */ - template> - class LastReducer : public ReducerBase { - public: - using utc_nanos = xo::time::utc_nanos; + public: + /* if sim, deliver event when simulation clock reaches + * .trigger_tm; .trigger_tm can be earlier than .ev time + */ + utc_nanos trigger_tm_; + /* event to deliver */ + Event ev_; + }; - public: - LastReducer() = default; - LastReducer(EventTimeFn const & evtfn) : ReducerBase(evtfn) {} + /* reducer that just remembers the last event + * + * Require: + * - Event is null-contructible + * - Event is copyable + * + * LastReducer provides reentrancy support. This support doesn't operate + * if Event copy is not deep, e.g. for Event = rpn + * + * .include_event() + * /-------\ -----------------> /------\ + * | empty | | full | + * \-------/ <----------------- \------/ + * . .annex_one() . + * . . + * .is_empty()=true .is_empty()=false + */ + template> + class LastReducer : public ReducerBase { + public: + using utc_nanos = xo::time::utc_nanos; - bool is_empty() const { return empty_flag_; } - /* require: .is_empty() = false */ - utc_nanos next_tm() const { - return this->last_ev_[this->last_ix_].trigger_tm_; - //return this->event_tm(this->last_ev_[this->last_ix_]); - } - /* #of events stored in this reducer (0 or 1) */ - uint32_t n_event() const { return this->empty_flag_ ? 0 : 1; } + public: + LastReducer() = default; + LastReducer(EventTimeFn const & evtfn) : ReducerBase(evtfn) {} - Event const & last_annexed_ev() const { - return this->last_ev_[1 - this->last_ix_].ev_; - } + bool is_empty() const { return empty_flag_; } + /* require: .is_empty() = false */ + utc_nanos next_tm() const { + return this->last_ev_[this->last_ix_].trigger_tm_; + //return this->event_tm(this->last_ev_[this->last_ix_]); + } + /* #of events stored in this reducer (0 or 1) */ + uint32_t n_event() const { return this->empty_flag_ ? 0 : 1; } - EventRecd & include_event_aux(Event const & ev) { - EventRecd & evr - = this->last_ev_[this->last_ix_]; + Event const & last_annexed_ev() const { + return this->last_ev_[1 - this->last_ix_].ev_; + } - if (this->empty_flag_) { - /* evr.trigger_tm will be preserved across - * successive calls to .include_event(); - * until .annex_one() - */ - evr.trigger_tm_ = this->event_tm(ev); + EventRecd & include_event_aux(Event const & ev) { + EventRecd & evr + = this->last_ev_[this->last_ix_]; - this->empty_flag_ = false; - } + if (this->empty_flag_) { + /* evr.trigger_tm will be preserved across + * successive calls to .include_event(); + * until .annex_one() + */ + evr.trigger_tm_ = this->event_tm(ev); - return evr; - } /*include_event_aux*/ + this->empty_flag_ = false; + } - void include_event(Event const & ev) { - EventRecd & evr - = this->include_event_aux(ev); + return evr; + } /*include_event_aux*/ - evr.ev_ = ev; - } /*include_event*/ + void include_event(Event const & ev) { + EventRecd & evr + = this->include_event_aux(ev); - void include_event(Event && ev) { - EventRecd & evr - = this->include_event_aux(ev); - - evr.ev_ = std::move(ev); - } /*include_event*/ + evr.ev_ = ev; + } /*include_event*/ - Event & annex_one() { - std::uint32_t annexed_ix = this->last_ix_; + void include_event(Event && ev) { + EventRecd & evr + = this->include_event_aux(ev); - /* since .empty_flag is true, - * next call to .include_event_aux() will - * capture new timestamp - */ - this->empty_flag_ = true; - this->last_ix_ = (1 - this->last_ix_); + evr.ev_ = std::move(ev); + } /*include_event*/ - return this->last_ev_[annexed_ix].ev_; - } /*annex_one*/ + Event & annex_one() { + std::uint32_t annexed_ix = this->last_ix_; - // ----- Inherited from ReducerBase ----- + /* since .empty_flag is true, + * next call to .include_event_aux() will + * capture new timestamp + */ + this->empty_flag_ = true; + this->last_ix_ = (1 - this->last_ix_); - //utc_nanos event_tm(Event const & ev) const { return this->event_tm_fn_(ev); } + return this->last_ev_[annexed_ix].ev_; + } /*annex_one*/ - private: - /* true when reducer contains 0 queued events, - * not counting any annexed event - */ - bool empty_flag_ = true; + // ----- Inherited from ReducerBase ----- - /* .last_ev[.last_ix] updated by .include_event() - */ - std::uint32_t last_ix_ = 0; - /* remember two events - * (a) a single queued event (updated by .include_event()) - * (b) a single removed event (reported by .annex_one()) - * - * roles of .last_ev[0], .last_ev[1] reverse each time .annex_one() runs - */ - std::array, 2> last_ev_; - }; /*LastReducer*/ - } /*namespace reactor*/ + //utc_nanos event_tm(Event const & ev) const { return this->event_tm_fn_(ev); } + + private: + /* true when reducer contains 0 queued events, + * not counting any annexed event + */ + bool empty_flag_ = true; + + /* .last_ev[.last_ix] updated by .include_event() + */ + std::uint32_t last_ix_ = 0; + /* remember two events + * (a) a single queued event (updated by .include_event()) + * (b) a single removed event (reported by .annex_one()) + * + * roles of .last_ev[0], .last_ev[1] reverse each time .annex_one() runs + */ + std::array, 2> last_ev_; + }; /*LastReducer*/ + } /*namespace reactor*/ } /*namespace xo*/ /* end LastReducer.hpp */ diff --git a/include/xo/reactor/SecondarySource.hpp b/include/xo/reactor/SecondarySource.hpp index c58c2e92..3b10de63 100644 --- a/include/xo/reactor/SecondarySource.hpp +++ b/include/xo/reactor/SecondarySource.hpp @@ -2,9 +2,8 @@ #pragma once -//#include "time/Time.hpp" +#include "EventSource.hpp" #include "Sink.hpp" -//#include "xo/reactor/DirectSource.hpp" #include "Reactor.hpp" #include "HeapReducer.hpp" #include "xo/callback/CallbackSet.hpp" From 10d49e511cc4f92e131c518721b6bdade27edbdf Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 12:02:52 -0400 Subject: [PATCH 0387/2524] bugfix: spelling - must use Eigen3 w/ find_package() on linux --- src/kalmanfilter/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kalmanfilter/CMakeLists.txt b/src/kalmanfilter/CMakeLists.txt index 30ca8c02..b7036526 100644 --- a/src/kalmanfilter/CMakeLists.txt +++ b/src/kalmanfilter/CMakeLists.txt @@ -28,4 +28,4 @@ xo_dependency(${SELF_LIB} reactor) # ---------------------------------------------------------------- # external dependencies -xo_external_target_dependency(${SELF_LIB} eigen3 Eigen3::Eigen) +xo_external_target_dependency(${SELF_LIB} Eigen3 Eigen3::Eigen) From 9bb3f8c668f09d5a075a8d62584187203719388a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 12:30:37 -0400 Subject: [PATCH 0388/2524] kalmanfilter: genx symlinks -> canned baseline data --- utest/CMakeLists.txt | 5 +- utest/KalmanFilter.test.cpp | 3 - utest/utestdata/filter/kalman-revert1 | 100 ++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 utest/utestdata/filter/kalman-revert1 diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 9f21bfa4..a118b8a7 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -13,9 +13,12 @@ add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) target_code_coverage(${SELF_EXE} AUTO ALL) # ---------------------------------------------------------------- -# create convenience symlink to canned data +# create convenience symlink from build dir back to canned data. +# This is ok since we don't implement install for unit tests #create_symlink("${CMAKE_SOURCE_DIR}/utestdata/" "src/filter/utest/utestdata") +file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/utest/utestdata) +file(CREATE_LINK ${PROJECT_SOURCE_DIR}/utest/utestdata/filter ${PROJECT_BINARY_DIR}/utest/utestdata/filter SYMBOLIC) # ---------------------------------------------------------------- # generic project dependency diff --git a/utest/KalmanFilter.test.cpp b/utest/KalmanFilter.test.cpp index 679f461f..c0b35601 100644 --- a/utest/KalmanFilter.test.cpp +++ b/utest/KalmanFilter.test.cpp @@ -29,9 +29,6 @@ namespace xo { using xo::ref::rp; using xo::log_level; using logutil::matrix; - //using logutil::scope; - //using logutil::tostr; - //using logutil::xtag; using xo::print::ccs; using Eigen::MatrixXd; using Eigen::VectorXd; diff --git a/utest/utestdata/filter/kalman-revert1 b/utest/utestdata/filter/kalman-revert1 new file mode 100644 index 00000000..343af232 --- /dev/null +++ b/utest/utestdata/filter/kalman-revert1 @@ -0,0 +1,100 @@ +step z0 x0 P00 K0 +1 1.33055 1.28103 0.195775 0.783099 +2 1.26559 1.2664 0.103557 0.414227 +3 0.790814 1.1272 0.0680813 0.272325 +4 0.173362 0.933668 0.0493859 0.197543 +5 2.12197 1.11662 0.0378989 0.151595 +6 1.00197 1.09766 0.0301647 0.120659 +7 -0.312247 0.954347 0.0246315 0.0985261 +8 1.76439 1.02286 0.020499 0.081996 +9 0.518422 0.986867 0.0173123 0.0692491 +10 0.756683 0.973864 0.0147938 0.0591754 +11 -0.609708 0.894249 0.0127646 0.0510585 +12 0.330435 0.874259 0.011104 0.0444159 +13 1.03713 0.886639 0.00972751 0.03891 +14 -0.994029 0.827609 0.00857454 0.0342982 +15 1.58353 0.858947 0.00760022 0.0304009 +16 0.494725 0.855945 0.00677073 0.0270829 +17 1.0205 0.866962 0.00606004 0.0242401 +18 1.98651 0.897865 0.00544782 0.0217913 +19 1.70559 0.918761 0.00491797 0.0196719 +20 1.44881 0.932201 0.00445755 0.0178302 +21 2.16318 0.955508 0.00405605 0.0162242 +22 0.353787 0.948782 0.00370485 0.0148194 +23 1.72676 0.961879 0.00339684 0.0135874 +24 -1.55312 0.932313 0.00312606 0.0125043 +25 -0.523229 0.918847 0.00288753 0.0115501 +26 1.81676 0.932476 0.00267702 0.0107081 +27 1.65387 0.943006 0.00249094 0.00996377 +28 -0.766534 0.929922 0.00232623 0.00930491 +29 1.1143 0.935004 0.00218024 0.00872095 +30 0.908743 0.938011 0.0020507 0.00820282 +31 1.80832 0.947825 0.00193566 0.00774263 +32 1.61306 0.955293 0.00183339 0.00733354 +33 1.10591 0.958563 0.0017424 0.00696961 +34 1.54404 0.964512 0.0016614 0.00664561 +35 1.47595 0.969526 0.00158925 0.00635699 +36 -1.16643 0.958012 0.00152494 0.00609975 +37 2.772 0.970748 0.00146759 0.00587036 +38 0.0147533 0.966786 0.00141643 0.00566572 +39 1.5648 0.971716 0.00137077 0.00548308 +40 0.758209 0.971987 0.00133001 0.00532003 +41 0.602221 0.971467 0.0012936 0.00517441 +42 1.02795 0.973171 0.00126108 0.00504433 +43 1.61126 0.977651 0.00123203 0.0049281 +44 3.23237 0.98964 0.00120606 0.00482423 +45 -1.20629 0.979766 0.00118284 0.00473137 +46 2.34054 0.987098 0.00116209 0.00464835 +47 1.886 0.991852 0.00114353 0.00457412 +48 1.62241 0.9951 0.00112693 0.00450773 +49 0.10733 0.991395 0.00111209 0.00444835 +50 0.111089 0.987954 0.00109881 0.00439523 +51 -0.045289 0.984062 0.00108693 0.00434771 +52 0.339478 0.98208 0.0010763 0.0043052 +53 -0.109018 0.978316 0.00106679 0.00426715 +54 -1.15684 0.970358 0.00105828 0.00423311 +55 -0.289587 0.966538 0.00105066 0.00420265 +56 1.04772 0.968543 0.00104385 0.00417538 +57 -0.0931435 0.965703 0.00103774 0.00415098 +58 2.56415 0.974011 0.00103228 0.00412914 +59 0.352559 0.972751 0.0010274 0.00410959 +60 0.173725 0.970838 0.00102302 0.00409209 +61 0.80765 0.971625 0.00101911 0.00407643 +62 2.41789 0.978913 0.0010156 0.0040624 +63 2.63322 0.986663 0.00101246 0.00404985 +64 2.59753 0.993833 0.00100965 0.00403861 +65 0.530642 0.992274 0.00100714 0.00402855 +66 1.24782 0.993686 0.00100489 0.00401955 +67 0.695031 0.992802 0.00100287 0.00401149 +68 2.80672 1.00042 0.00100107 0.00400427 +69 0.122651 0.996894 0.000999451 0.0039978 +70 1.22154 0.997945 0.000998005 0.00399202 +71 0.269797 0.995145 0.00099671 0.00398684 +72 1.78662 0.998538 0.00099555 0.0039822 +73 0.4817 0.996555 0.000994512 0.00397805 +74 1.05221 0.996948 0.000993582 0.00397433 +75 1.75822 1.00012 0.00099275 0.003971 +76 1.31536 1.00137 0.000992005 0.00396802 +77 1.29006 1.00244 0.000991338 0.00396535 +78 0.857458 1.00175 0.000990741 0.00396296 +79 0.269498 0.998761 0.000990206 0.00396082 +80 0.131184 0.995388 0.000989727 0.00395891 +81 0.736283 0.994592 0.000989298 0.00395719 +82 -0.32478 0.989642 0.000988914 0.00395566 +83 1.3352 0.991525 0.00098857 0.00395428 +84 0.279734 0.989133 0.000988263 0.00395305 +85 1.74807 0.992673 0.000987987 0.00395195 +86 1.82522 0.996328 0.00098774 0.00395096 +87 -0.281213 0.991464 0.000987519 0.00395008 +88 1.61161 0.994338 0.000987322 0.00394929 +89 2.63571 1.0011 0.000987144 0.00394858 +90 -0.139888 0.996542 0.000986986 0.00394794 +91 3.48906 1.00655 0.000986844 0.00394738 +92 2.01113 1.01019 0.000986717 0.00394687 +93 1.84914 1.01299 0.000986603 0.00394641 +94 0.480262 1.01025 0.000986501 0.003946 +95 1.45288 1.01148 0.00098641 0.00394564 +96 -0.0838873 1.00659 0.000986328 0.00394531 +97 -0.570744 1.00004 0.000986255 0.00394502 +98 -0.214659 0.995244 0.000986189 0.00394476 +99 1.7904 0.998618 0.000986131 0.00394452 From 5ae6f41e63bae9abd468bcc787e314ca0542f3f3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 13:34:53 -0400 Subject: [PATCH 0389/2524] + xo_cxx_toplevel_options() to consolidate boilerplate --- cmake/xo_macros/xo-project-macros.cmake | 2 ++ cmake/xo_macros/xo_cxx.cmake | 30 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 cmake/xo_macros/xo-project-macros.cmake diff --git a/cmake/xo_macros/xo-project-macros.cmake b/cmake/xo_macros/xo-project-macros.cmake new file mode 100644 index 00000000..a18c9b8d --- /dev/null +++ b/cmake/xo_macros/xo-project-macros.cmake @@ -0,0 +1,2 @@ +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 2f9560db..1e308aa0 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -1,4 +1,16 @@ +macro(xo_cxx_toplevel_options) + enable_language(CXX) + xo_toplevel_compile_options() + xo_toplevel_testing_options() +endmacro() + +macro(xo_toplevel_testing_options) + enable_testing() + add_code_coverage() + add_code_coverage_all_targets(EXCLUDE /nix/store* utest/*) +endmacro() + macro(xo_toplevel_compile_options) define_property( TARGET @@ -214,6 +226,24 @@ endmacro() # e.g. # - target=xo_pyutil cmake target name for this library # +macro(xo_add_headeronly_library4 target projectTargets) + add_library(${target} INTERFACE) + + set_property( + TARGET ${target} + PROPERTY xo_deps "${target}") + set_property( + TARGET ${target} + PROPERTY xo_srcdir ${PROJECT_SOURCE_DIR}) + set_property( + TARGET ${target} + PROPERTY xo_bindir ${PROJECT_BINARY_DIR}) + + xo_include_headeronly_options(${target}) + + xo_install_library4(${target} ${projectTargets}) +endmacro() + macro(xo_add_headeronly_library target) add_library(${target} INTERFACE) From 64842065a39588695f4aba6eed712b3a4c55c3b4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 13:36:51 -0400 Subject: [PATCH 0390/2524] bugfix: + install for xo-project-macros.cmake --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 197416e8..e7e30b56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ set(XO_PROJECT_NAME xo_macros) install( FILES + "cmake/xo_macros/xo-project-macros.cmake" "cmake/xo_macros/xo_cxx.cmake" "cmake/xo_macros/code-coverage.cmake" PERMISSIONS OWNER_READ GROUP_READ WORLD_READ From e281bc9213ec0b83711a0c40b564d968e2f9d0d6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 13:46:10 -0400 Subject: [PATCH 0391/2524] initial implementation --- CMakeLists.txt | 20 + cmake/xo_distributionConfig.cmake.in | 4 + include/xo/distribution/Distribution.hpp | 20 + include/xo/distribution/Empirical.hpp | 41 + include/xo/distribution/ExplicitDist.hpp | 748 ++++++++++++++++++ include/xo/distribution/Exponential.hpp | 94 +++ include/xo/distribution/KolmogorovSmirnov.hpp | 257 ++++++ include/xo/distribution/Normal.hpp | 51 ++ include/xo/distribution/StdEmpirical.hpp | 226 ++++++ include/xo/distribution/Uniform.hpp | 47 ++ 10 files changed, 1508 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/xo_distributionConfig.cmake.in create mode 100644 include/xo/distribution/Distribution.hpp create mode 100644 include/xo/distribution/Empirical.hpp create mode 100644 include/xo/distribution/ExplicitDist.hpp create mode 100644 include/xo/distribution/Exponential.hpp create mode 100644 include/xo/distribution/KolmogorovSmirnov.hpp create mode 100644 include/xo/distribution/Normal.hpp create mode 100644 include/xo/distribution/StdEmpirical.hpp create mode 100644 include/xo/distribution/Uniform.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..467f52cc --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +# xo-distribution/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_distribution VERSION 1.0) + +# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(xo_macros/xo-project-macros) + +xo_cxx_toplevel_options() + +#add_subdirectory(example) +#add_subdirectory(uteest) + +xo_add_headeronly_library4(xo_distribution ${PROJECT_NAME}Targets) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- + +#install(Targets ex1 DESTINATION bin/distribution/example) diff --git a/cmake/xo_distributionConfig.cmake.in b/cmake/xo_distributionConfig.cmake.in new file mode 100644 index 00000000..3c6c4bd4 --- /dev/null +++ b/cmake/xo_distributionConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/xo_distributionTargets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/distribution/Distribution.hpp b/include/xo/distribution/Distribution.hpp new file mode 100644 index 00000000..5768e9d9 --- /dev/null +++ b/include/xo/distribution/Distribution.hpp @@ -0,0 +1,20 @@ +/* @file Distribution.hpp */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" + +namespace xo { + namespace distribution { + /* abstract api for a cumulative probability distribution. + * over supplied Domain + */ + template + class Distribution : public ref::Refcount { + public: + virtual double cdf(Domain const & x) const = 0; + }; /*Distribution*/ + } /*namespace distribution*/ +} /*namespace xo*/ + +/* end Distribution.hpp */ diff --git a/include/xo/distribution/Empirical.hpp b/include/xo/distribution/Empirical.hpp new file mode 100644 index 00000000..2b7b9d67 --- /dev/null +++ b/include/xo/distribution/Empirical.hpp @@ -0,0 +1,41 @@ +/* @file Empirical.hpp */ + +#pragma once + +#include "xo/distribution/Distribution.hpp" +#include "xo/ordinaltree/RedBlackTree.hpp" +#include "xo/indentlog/scope.hpp" +#include +#include + +namespace xo { + namespace distribution { + + /* representation for counter, + * recording #of samples with the same value + */ + using CounterRep = uint32_t; + + /* counter; for use with StdEmpirical distribution below + */ + class Counter { + public: + Counter() = default; + Counter(CounterRep n) : count_(n) {} + + CounterRep count() const { return count_; } + + void incr() { ++count_; } + + operator CounterRep () const { return count_; } + + Counter & operator+=(CounterRep n) { count_ += n; return *this; } + + private: + CounterRep count_ = 0; + }; /*Counter*/ + + } /*namespace disitribution*/ +} /*namespace xo*/ + +/* end Empirical.hpp */ diff --git a/include/xo/distribution/ExplicitDist.hpp b/include/xo/distribution/ExplicitDist.hpp new file mode 100644 index 00000000..516413e1 --- /dev/null +++ b/include/xo/distribution/ExplicitDist.hpp @@ -0,0 +1,748 @@ +/* file ExplicitDist.hpp + * + * author: Roland Conybeare, Oct 2022 + */ + +#pragma once + +#include "Distribution.hpp" +#include "Normal.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/vector.hpp" +#include "xo/indentlog/print/tostr.hpp" +#include +#include +#include + +namespace xo { + using xo::xtag; + + namespace distribution { + class ProbabilityBucket { + public: + ProbabilityBucket() = default; + + double weight() const { return wt_; } + double cdf() const { return cdf_; } + + /* note: when calling this, must invalidate ExplicitDist.cdf_valid_flag */ + void scale_weight(double k) { this->wt_ *= k; } + + void assign_weight(double w) { this->wt_ = w; } + /* implementation method: only ExplicitDist.renormalize() should call this */ + void assign_cdf(double x) { this->cdf_ = x; } + + void display(std::ostream & os) const { + using xo::xtag; + + os << "wt_) + << xtag("cdf", this->cdf_) + << ">"; + } /*display*/ + + private: + /* probability assigned to this bucket. + * updated in-place by ExplicitDist.include_sample() + */ + double wt_ = 0.0; + /* cumulative probability assigned to this bucket b + * and all buckets b[i] for domain values < b + * + * invalidated by ExplicitDist.scale_bucket() + * see ExplicitDist. + */ + double cdf_ = 0.0; + }; /*ProbabilityBucket*/ + + inline std::ostream & operator<<(std::ostream & os, ProbabilityBucket const & x) { + x.display(os); + return os; + } /*operator<<*/ + + namespace detail { + /* three kinds of index here: + * 1. lo: non-negative index into ExplicitDist.lo_v_[] + * 2. hi: non-negative index into ExplicitDist.hi_v_[] + * 3. signed: if >=0: index into ExplicitDist.hi_v_[] + * if <0: index into ExplicitDist.lo_v_[] + * + * with lz=.lo_v.size(), hz=hi_v.size(): + * + * .hi_v[0] .hi_v[hz-1] + * | v v + * | + * +--+--+ +--+--|--+--+ +--+ + * | | | ... | | | | | ... | | + * +--+--+ +--+--|--+--+ +--+ + * ^ ^ + * .lo_v[lz-1] .lo_v[0] + * + * ^ ^ ^ ^ + * signed -lz -1| 0 ... hz-1 + */ + class ExplicitDistIndexUtil { + public: + static size_t signed2hi(int32_t signed_ix) { return signed_ix; } + static size_t signed2lo(int32_t signed_ix) { return -signed_ix - 1; } + static int32_t lo2signed(size_t lo_ix) { return -static_cast(lo_ix)-1; } + static int32_t hi2signed(size_t hi_ix) { return hi_ix; } + + static ProbabilityBucket const * signed2bucket_c(int32_t signed_ix, + std::vector const * lo_v, + std::vector const * hi_v) { + if (signed_ix >= 0) { + size_t hi_ix = signed2hi(signed_ix); + + if (hi_v && (hi_ix < hi_v->size())) + return &(*hi_v)[hi_ix]; + } else { + size_t lo_ix = signed2lo(signed_ix); + + if (lo_v && (lo_ix < lo_v->size())) + return &(*lo_v)[lo_ix]; + } + + return nullptr; + } /*signed2bucket_c*/ + + static ProbabilityBucket * signed2bucket(int32_t signed_ix, + std::vector * lo_v, + std::vector * hi_v) { + ProbabilityBucket const * b = signed2bucket_c(signed_ix, lo_v, hi_v); + return const_cast(b); + } /*signed2bucket*/ + + }; /*ExplicitDistIndexUtil*/ + + class ExplicitDistIterator : public ExplicitDistIndexUtil { + public: + ExplicitDistIterator() = default; + ExplicitDistIterator(int32_t signed_ix, + std::vector * lo_v, + std::vector * hi_v) + : signed_ix_{signed_ix}, p_lo_v_{lo_v}, p_hi_v_{hi_v} {} + + ProbabilityBucket & operator*() { + ProbabilityBucket * pb = signed2bucket(this->signed_ix_, + this->p_lo_v_, + this->p_hi_v_); + if (!pb) { + throw std::runtime_error("ExplicitDistIterator: attempt to deref invalid iterator"); + } + + return *pb; + } /*operator**/ + + ExplicitDistIterator & operator++() { ++(this->signed_ix_); return *this; } + ExplicitDistIterator & operator--() { --(this->signed_ix_); return *this; } + + private: + /* signed iterator */ + int32_t signed_ix_ = 0; + /* will be ExplicitDist.lo_v[] */ + std::vector * p_lo_v_ = nullptr; + /* will be ExplicitDist.hi_v[] */ + std::vector * p_hi_v_ = nullptr; + }; /*ExplicitDistIterator*/ + } /*namespace detail*/ + + /* explicit distribution with buckets. + * (if you want to avoid bucketing at the expense of memory, + * see StdEmpirical) + * + * sample-independent buckets can be faster to the extent + * {#of buckets} << {#of samples} + * + * in particular + * | StdEmpirical | PackedEmpirical + * ------------------------------+--------------+----------------- + * update existing sample/bucket | O(log(n)) | O(1) + * create new sample/bucket | O(log(n)) | O(log(n)) + * cdf | O(log(n)) | O(n) + * + * PackedEmpirical offers scaling + renormalization + * + * since .cdf() is slow, .ks_stat_1sided() is supported only in StdEmpirical. + * (could generalize in future to some other implementation with fast .cdf()) + * + * Require: + * Domain is something with metric, e.g. int|double. + * categorical domain not supported here. + * + * see also: statistics/Histogram. Histogram keeps more per-bucket statistics + */ + template + class ExplicitDist : public Distribution, public detail::ExplicitDistIndexUtil { + public: + using WeightVector = std::vector; + using iterator = detail::ExplicitDistIterator; + + public: + static ref::rp make(Domain bucket_dx, Domain ref_value) { + return new ExplicitDist(bucket_dx, ref_value); + } + /* create distribution with n buckets of width bucket_dx, + * covering range [ref_value, ref_value + n * bucket_dx] + */ + static ref::rp make_n(size_t n, Domain bucket_dx, Domain ref_value) { + return new ExplicitDist(n, bucket_dx, ref_value); + } + + size_t n_bucket() const { return this->lo_v_.size() + this->hi_v_.size(); } + + /* lub domain value with .cdf(lo) = 0 */ + Domain lo() const { + std::size_t lz = this->lo_v_.size(); + + return this->ref_value_ - lz * this->bucket_dx_; + } /*lo*/ + + /* glb domain value with .cdf(hi) = 1 */ + Domain hi() const { + std::size_t hz = this->hi_v_.size(); + + return this->ref_value_ + hz * this->bucket_dx_; + } /*hi*/ + + Domain bucket_lo(int32_t signed_ix) const { + return this->ref_value_ + signed_ix * this->bucket_dx_; + } /*bucket_lo*/ + + Domain bucket_mid(int32_t signed_ix) const { + return this->ref_value_ + (0.5 + signed_ix) * this->bucket_dx_; + } /*bucket_mid*/ + + Domain bucket_hi(int32_t signed_ix) const { + return this->bucket_lo(signed_ix + 1); + } /*bucket_hi*/ + + iterator begin() { return iterator(lo2signed(this->lo_v_.size() - 1), + &(this->lo_v_), + &(this->hi_v_)); } + iterator end() { return iterator(hi2signed(this->hi_v_.size()), + &(this->lo_v_), + &(this->hi_v_)); } + + /* probability density at domain value x */ + double density(Domain const & x) const { + /* careful! may need to renormalize */ + { + ExplicitDist * self = const_cast *>(this); + + self->check_renormalize(); + } + + auto v = this->lookup_bucket(x); + ProbabilityBucket const * b = v.first; + + if (b) { + /* probability density is constant within each bucket */ + return b->weight() / this->bucket_dx_; + } else { + return 0.0; + } + } /*density*/ + + /* pair {bucket glb, density} for all buckets, in increasing domain order */ + std::vector> density_v() const { + /* careful! may need to renormalize */ + { + ExplicitDist * self = const_cast *>(this); + + self->check_renormalize(); + } + + size_t n = this->n_bucket(); + std::vector> retval(n); + + double w2d = 1.0 / this->bucket_dx_; + + for(size_t i=0; ibucket_lo(i); + ProbabilityBucket const * b = this->lookup_signed_bucket(i); + + assert(b); + + double density = b->weight() * w2d; + + retval[i] = std::make_pair(lo, density); + } + + return retval; + } /*density_v*/ + + /* return: + * .first signed bucket index ix; + * refers to .lo_v[1-ix] if ix<0; + * refers to .hi_v[+ix] if ix>=0 + * .second fractional weight within bucket associated with x. + * not scaled by ProbabilityWeight.weight + */ + std::pair signed_bucket_index(Domain const & x) const { + double ix_f = ::floor((x - this->ref_value_) / this->bucket_dx_); + int32_t ix = ix_f; + + /* + * ^ + * |............ + * | : | + * | wt : | unit area + * | : | + * +-----------+ + * bucket_lo ^ bucket_lo + .bucket_dx + * x + */ + + Domain bucket_lo = this->ref_value_ + (ix * this->bucket_dx_); + double wt = (x - bucket_lo) / this->bucket_dx_; + + return std::make_pair(ix, wt); + } /*signed_bucket_index*/ + + ProbabilityBucket const * lookup_signed_bucket(int32_t signed_ix) const { + return signed2bucket_c(signed_ix, &(this->lo_v_), &(this->hi_v_)); + } /*lookup_signed_bucket*/ + + /* non-const version with write access */ + ProbabilityBucket * lookup_signed_bucket(int32_t signed_ix) { + return signed2bucket(signed_ix, &(this->lo_v_), &(this->hi_v_)); + } /*lookup_signed_bucket*/ + + /* return null pointer if bucket that would contain x not represented + * .second is fractional weight (relative to unity) within selected bucket + */ + std::pair lookup_bucket(Domain const & x) const { + /* signed index. + * ix>=0 => .lo_v[] + * ix< 0 => .hi_v[] + */ + std::pair v = this->signed_bucket_index(x); + + int32_t ix = v.first; + double fraction_wt = v.second; + ProbabilityBucket const * pw = this->lookup_signed_bucket(ix); + + return std::make_pair(pw, fraction_wt); + } /*lookup_bucket*/ + + /* non-const version */ + std::pair lookup_bucket(Domain const & x) { + ExplicitDist const * c_self = this; + + std::pair v = c_self->lookup_bucket(x); + ProbabilityBucket * b = const_cast(v.first); + double fraction_wt = v.second; + + return std::make_pair(b, fraction_wt); + } /*lookup_bucket*/ + + /* like .lookup_bucket(), but create bucket if not already present + * .second reports fractional weight (relative to unity) within bucket + * that contains x. + */ + std::pair establish_bucket(Domain const & x) { + /* signed index. + * ix>=0 => .lo_v[] + * ix< 0 => .hi_v[] + */ + std::pair v = this->signed_bucket_index(x); + + int32_t ix = v.first; + double fraction_wt = v.second; + ProbabilityBucket * pw = nullptr; + + if (ix >= 0) { + size_t hi_ix = signed2hi(ix); + + if (hi_ix >= this->hi_v_.size()) { + /* need to expand .hi_v[] */ + this->hi_v_.resize(hi_ix+1); + } + + pw = &(this->hi_v_[hi_ix]); + } else { + size_t lo_ix = signed2lo(ix); + + if (lo_ix >= this->lo_v_.size()) { + /* need to expand .lo_v[] */ + this->lo_v_.resize(lo_ix+1); + } + + pw = &(this->lo_v_[lo_ix]); + } + + return std::make_pair(pw, fraction_wt); + } /*establish_bucket*/ + + void scale_bucket_by_signed_index(int32_t signed_ix, double k) { + ProbabilityBucket * b = this->lookup_signed_bucket(signed_ix); + + if (b) { + b->scale_weight(k); + + this->cdf_valid_flag_ = false; + } else { + /* if bucket isn't represented, then corresponding weight is zero, + * both before and after scaling. In this special case + * representation didn't change -> don't invalidate cdf + */ + } + } /*scale_bucket_by_signed_index*/ + + /* scale probability assigned to bucket for domain value x, by factor k */ + void scale_bucket(Domain const & x, double k) { + assert(k >= 0.0); + + std::pair v = this->signed_bucket_index(x); + + this->scale_bucket_by_signed_index(v.first, k); + } /*scale_bucket*/ + + /* scale probability weight for buckets in [lo, hi] by fn(p) + * for a point p in each bucket, however: + * under no circumstances try to evaluate fn() outside [lo, hi] + * (relevant if either lo/hi fall inside a bucket) + */ + template + void scale_interval(Domain const & lo, + Domain const & hi, + Function && fn) + { + XO_SCOPE_DISABLED(lscope); + + /* note: using inclusive upper index bounds here; + * variying from idiomatic c++ style for symmetry + */ + + int32_t min_bucket_ix = lo2signed(this->lo_v_.size() - 1); + int32_t max_bucket_ix = hi2signed(this->hi_v_.size() - 1); + + int32_t lo_ix = this->signed_bucket_index(lo).first; + int32_t hi_ix = this->signed_bucket_index(hi).first; + + /* start_ix: lowest explicit bucket associating with [lo, hi) */ + int32_t start_ix = std::max(min_bucket_ix, lo_ix + 1); + /* end_ix: highest explicit bucket associating with [lo, hi) */ + int32_t end_ix = std::min(max_bucket_ix, hi_ix); + + if (lscope.enabled()) { + lscope.log(xtag("min_bucket_ix", min_bucket_ix), + xtag("max_bucket_ix", max_bucket_ix), + xtag("lo_ix", lo_ix), + xtag("hi_ix", hi_ix), + xtag("start_ix", start_ix), + xtag("end_ix", end_ix)); + } + + /* for endpoints: avoid evaluating fn() outside [lo, hi] */ + if (min_bucket_ix <= lo_ix) { + double lo_k = fn(lo); + + if (lscope.enabled()) + lscope.log("A", xtag("lo_ix", lo_ix), xtag("lo_k", lo_k)); + + this->scale_bucket_by_signed_index(lo_ix, lo_k); + } + + for(int32_t ix = start_ix; ix <= end_ix; ++ix) { + double k = fn(this->bucket_mid(ix)); + + if (lscope.enabled()) + lscope.log("B", xtag("ix", ix), xtag("k", k)); + + this->scale_bucket_by_signed_index(ix, k); + } + + /* for endpoints: avoid evaluating fn() outside [lo, hi] */ + if (hi_ix <= max_bucket_ix) { + double hi_k = fn(hi); + + if (lscope.enabled()) + lscope.log("C", xtag("hi_ix", hi_ix), xtag("hi_k", hi_k)); + + this->scale_bucket_by_signed_index(hi_ix, hi_k); + } + } /*scale_interval*/ + + template + void scale_all(Function && fn) + { + this->scale_interval(this->lo(), this->hi(), fn); + } /*scale_all*/ + + /* convenience: scale by scaled normal cdf. + * + * support use case where: + * 1. explicit dist represents distribution of asset value; + * 2. assume one party A to trade/order has noisy signal, + * with normally-distributed error around (unknown) true value s, + * with variance o^2; + * 3. observe a trade/order at some price p, + * giving us one-sided information about A's knowledge in two scenarios: + * i. A trades + * ii. A does not trade + * + * We will be applying Bayes' rule to update prior. + * If buy(p) = {A buys}, + * N(x) is cumulative normal distribution: + * N(x) -> 0 as x -> -oo; + * N(x) -> 1 as x -> +oo; + * + * -1/2 2 + * (d/dx)N(x) = (2.pi) . exp(-x /2) + * then: + * P{s=s'} + * P{s=s'|buy(p)} = ---------.P{buy(p)|s=s'} + * P{buy(p)} + * + * P{s=s'} + * = ---------.N[(1/o)(s'-p)], + * P{buy(p)} + * + * In this scenario, P{s=s'} is our prior, represented by distribution *this. + * The unconditional probability P{buy(p)} is not a function of s', so: + * + * / + * | + * P{buy(p)} = | P{s=s'} . N[(1/o)(s'-p)] . ds' + * | + * / + * + * in other words can scale explicit prior *this by N[(1/o)(s'-p)] then renormalize + * so total probability weight is 1. + * + * Conversely, if event is {A sells}, similar argument leads to scaling + * by N[(1/o)(p-s')] + * + * sign. scale by N[(1/sigma).sign.(x-mean)] + */ + void scale_by_normal_cdf(int sign, Domain const & mean, Domain const & sigma) { + auto fn([sign, mean, sigma](Domain const & s) { + return Normal::cdf_impl(sign * (s - mean) / sigma); + }); + + this->scale_all(fn); + } /*scale_by_normal_cdf*/ + + // ----- inherited from Distribution ----- + + /* note: marked const; actually "logically const" */ + virtual double cdf(Domain const & x) const override { + /* .cdf() is slow here, because partial sums aren't stored + * --> have to sum over O(n) buckets + */ + { + ExplicitDist * self = const_cast *>(this); + + self->check_renormalize(); + } + + std::pair v = this->lookup_bucket(x); + + ProbabilityBucket const * b = v.first; + double fraction_wt = v.second; + + if (b) { + /* for bucket that x belongs to, treat as uniformly distributed + * fraction_wt = 1.0 -> 100% of b -> b.cdf() + * fraction_wt = 0.0 -> 0% of b -> b.cdf() - b.weight() + */ + return b->cdf() + (fraction_wt - 1.0) * b->weight(); + } else { + if (x >= this->ref_value_) { + /* x falls above farthest .hi_v[] */ + return 1.0; + } else { + /* x falls below farthest .lo_v[] */ + return 0.0; + } + } + } /*cdf*/ + + /* O(n). restores .cdf_valid_flag */ + void renormalize() { + /* [1] compute sum of probability weights + * [2] rescale all buckets so sum is 1. + * [3] restore .cdf_valid_flag + */ + + /* [1] */ + double lo_sum_prob = 0.0; + for (ProbabilityBucket & b : this->lo_v_) { + assert(lo_sum_prob >= 0.0); + lo_sum_prob += b.weight(); + } + + double hi_sum_prob = 0.0; + for (ProbabilityBucket & b : this->hi_v_) { + assert(hi_sum_prob >= 0.0); + hi_sum_prob += b.weight(); + } + + assert(lo_sum_prob + hi_sum_prob > 0.0); + + /* [2] */ + double renorm_factor = 1.0 / (lo_sum_prob + hi_sum_prob); + + { + double lo_tail = lo_sum_prob; + + /* iterate over buckets < .ref_value, in /descending/ domain order */ + for (ProbabilityBucket & b : this->lo_v_) { + b.scale_weight(renorm_factor); + b.assign_cdf(lo_tail); + + /* reduce tail after assign cdf to b, since + * lo_tail includes b's probability weight + */ + lo_tail -= b.weight(); + + /* numerical roundoff can cause this */ + if (lo_tail < 0.0) + lo_tail = 0.0; + } + } + + { + double cum_prob = lo_sum_prob; + + /* iterate over buckets >= .refvalue, in /ascending/ domain order */ + for (ProbabilityBucket & b : this->hi_v_) { + b.scale_weight(renorm_factor); + + /* increase cum_prob before assign cdf to b, + * since b.cdf should include b's probability weight + */ + cum_prob += b.weight(); + + /* numerical roundoff can cause this */ + if (cum_prob >= 1.0) + cum_prob = 1.0; + + b.assign_cdf(cum_prob); + } + } + + /* [3] */ + this->cdf_valid_flag_ = true; + } /*renormalize*/ + + void check_renormalize() { + if (!this->cdf_valid_flag_) { + this->renormalize(); + } + } /*check_renormalize*/ + + void display(std::ostream & os) const { + os << "cdf_valid_flag_); + os << xtag("bucket_dx", this->bucket_dx_); + os << xtag("ref_value", this->ref_value_); + os << xtag("lz", this->lo_v_.size()); + os << xtag("hz", this->hi_v_.size()); + os << xtag("lo_v", this->lo_v_); + os << xtag("hi_v", this->hi_v_); + os << ">"; + } /*display*/ + + std::string display_string() const { return xo::tostr(*this); } + + private: + ExplicitDist(Domain bucket_dx, Domain ref_value) + : cdf_valid_flag_{true}, + bucket_dx_{bucket_dx}, + ref_value_{ref_value}, + lo_v_{}, + hi_v_{1} + { + assert(bucket_dx_ > 0.0); + + /* must have at least one bucket, since need total probability weight = 1 */ + this->hi_v_[0].assign_weight(1.0); + this->hi_v_[0].assign_cdf(1.0); + } + + ExplicitDist(size_t n, Domain bucket_dx, Domain ref_value) + : cdf_valid_flag_{true}, + bucket_dx_{bucket_dx}, + ref_value_{ref_value}, + lo_v_{}, + hi_v_{n} + { + assert(bucket_dx > 0.0); + /* must have at least one bucket, since need total probability weight = 1 */ + assert(n > 0); + + double w = 1.0 / n; + + for(size_t i = 0; ihi_v_[i].assign_weight(w); + this->hi_v_[i].assign_cdf((i+1)*w); + } + } /*ctor*/ + + private: + /* with lz=.lo_v.size(), hz=hi_v.size(): + * + * .ref_value - .bucket_dx * lz + * | .ref_value + * | | .hi_v[0] .hi_v[hz-1] + * v v v v + * | + * +--+--+ +--+--|--+--+ +--+ + * | | | ... | | | | | ... | | + * +--+--+ +--+--|--+--+ +--+ + * | + * ^ ^ ^ + * .lo_v[lz-1] .lo_v[0] .ref_value + .bucket_dx * hz + * + */ + + /* .cdf_valid_flag: + * -> false whenever .scale_bucket() runs. + * -> true whenever .renormalize() runs. + * + * if false, then *this is 'not a probability distribution': + * Sum b[i].weight != 1.0 (summing over probability buckets .lo_v[], .hi_v[]) + * i + */ + bool cdf_valid_flag_ = true; + + /* width of each bucket */ + Domain bucket_dx_; + + /* natural value here would be 0. + * use .pos_v[] for values >= .ref_value + * use .neg_v[] for values < .ref_value + */ + Domain ref_value_; + + /* buckets for domain values < .ref_value + * + * with dx = .bucket_dx + * .lo_v[i] represents weights in range + * [.ref_value - (i+1) * dx, .ref_value - i * dx) + */ + WeightVector lo_v_; + + /* buckets for domain values > .ref_value + * + * with dx = .bucket_dx + * .hi_v[i] represents weights in range + * [.ref_value + i * dx, .ref_value + (i+1) * dx)) + */ + WeightVector hi_v_; + + }; /*ExplicitDist*/ + + template + inline std::ostream & + operator<<(std::ostream & os, ExplicitDist const & x) { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace distribution*/ +} /*namespace xo*/ + +/* end ExplicitDist.hpp */ diff --git a/include/xo/distribution/Exponential.hpp b/include/xo/distribution/Exponential.hpp new file mode 100644 index 00000000..f8dd2594 --- /dev/null +++ b/include/xo/distribution/Exponential.hpp @@ -0,0 +1,94 @@ +/* @file Exponential.hpp */ + +#pragma once + +#include "xo/distribution/Distribution.hpp" +#include +#include + +namespace xo { + namespace distribution { + /* Exponential probability distribution */ + class Exponential : public Distribution { + public: + explicit Exponential(double lm) : lambda_(lm) {} + + /* exponential probability density: + * + * -lm.x + * p(x) = { lm . e , x > 0 + * { 0 , x <= 0 + * + */ + static double density_impl(double lambda, double x) { + if(x <= 0.0) + return 0.0; + + return lambda * ::exp(-lambda * x); + } /*density_impl*/ + + /* exponential distribution: + * + * -lm.x + * F(x) = 1 - e , x > 0 + * F(x) = 0 , x <= 0 + */ + static double distr_impl(double lambda, double x) { + if(x <= 0.0) + return 0.0; + + return 1.0 - ::exp(-lambda * x); + } /*distr_impl*/ + + /* compute x: F(x)=y, where F(x) + * is the cumulative exponential probability distributio. + * + * -lm.x + * F(x) = 1 - e , x>0 + * + * -lm.x + * e = 1 - F(x) + * + * -lm.x = ln(1 - F(x)) + * + * x = -ln(1 - F(x)) / lm, 0 < F(x) < 1 + */ + static double distr_inverse_impl(double lambda, double Fx) { + if(Fx < 0.0) + return -1.0 * std::numeric_limits::infinity(); + if(Fx >= 1.0) + return +1.0 * std::numeric_limits::infinity(); + + return (-1.0 / lambda) * ::log(1.0 - Fx); + } /*distr_inverse_impl*/ + + double lambda() const { return lambda_; } + + double density(double x) const { + return density_impl(this->lambda_, x); + } /*density*/ + + double distribution(double x) const { + return distr_impl(this->lambda_, x); + } /*distribution*/ + + double distribution_inverse(double y) const { + return distr_inverse_impl(this->lambda_, y); + } /*distribution_inverse*/ + + // ----- inherited from Distribution ----- + + virtual double cdf(double const & x) const override { + return distribution(x); + } /*cdf*/ + + private: + /* intensity parameter. + * require: lambda > 0 + */ + double lambda_ = 1.0; + }; /*Exponential*/ + } /*namespace distribution*/ +} /*namespace xo*/ + +/* end Exponential.cpp */ diff --git a/include/xo/distribution/KolmogorovSmirnov.hpp b/include/xo/distribution/KolmogorovSmirnov.hpp new file mode 100644 index 00000000..ee0ec160 --- /dev/null +++ b/include/xo/distribution/KolmogorovSmirnov.hpp @@ -0,0 +1,257 @@ +/* @file KolmogorovSmirnov.hpp */ + +#pragma once + +#include "Distribution.hpp" +#include "xo/indentlog/scope.hpp" +#include +#include + +namespace xo { + /* Kolmogorov-Smirnov probability distribution */ + namespace distribution { + + class KolmogorovSmirnov : public Distribution { + public: + KolmogorovSmirnov() = default; + + /* kolmogorov-smirnov (KS) cumulative distribution + * + * cdf is defined by the series: + * + * +oo / j 2 2 \ + * P1(x) = 1 + 2 Sum | (-1) .exp(-2.j .x ) | + * j=1 \ / + * + * this converges rapidly for x > 1. expanding the first few terms: + * + * / 2 2 2 2 2 2 2 \ + * P1(x) ~= 1 + 2.| -exp(-2.x ) + exp(-2.2 .x ) - exp(-2.3 .x ) + exp(-2.4 .x ) | + * \ / + * + * / 2 2 4 2 9 2 16 \ + * = 1 + 2.| -exp(-2.x ) + exp(-2.x ) - exp(2.x ) + exp(-2.x ) | + * \ / + * + * / 4 9 16 \ + * = 1 + 2.| T(x) + T(x) + T(x) + T(x) | + * \ / + * + * with T(x) = .term_aux(1, x) + * + * with x=1: + * term1_aux(1, 1): -0.135335 + * term1_aux(2, 1): 3.35463e-4 + * term1_aux(3, 1): -1.52300e-8 + * term1_aux(4, 1): 1.26642e-14 + * term1_aux(5, 1): -1.92875e-22 + * + * with x=1.18: + * term1_aux(1, 1): -0.0617414 + * term1_aux(2, 1): 1.45314e-5 + * term1_aux(3, 1): -1.30374e-11 + * term1_aux(4, 1): 4.45890e-20 + * term1_aux(5, 1): -5.83124e-31 + * + * ---------------------------------------------------------------- + * + * There's an alternative series for the KS distribution, + * that converges rapidly for small x (x < ~1.18) + * + * / 2 2 \ + * sqrt(2.pi) +oo / | (2j - 1) .pi | \ + * P2(x) = ---------- . Sum | exp | - ------------ | | + * x j=1 \ | 2 | / + * \ 8.x / + * + * / 2 \ + * | pi | + * witu U(x) = exp | - ---- | + * | 2 | + * \ 8.x / + * we have + * / 2 \ + * sqrt(2.pi) +oo | (2j - 1) | + * P2(x) = ---------- . Sum | U(x) | + * x j=1 | | + * \ / + * + * / \ + * sqrt(2.pi) | 9 25 49 | + * = ---------- . | U(x) + U(x) + U(x) + U(x) + .. | + * x | | + * \ / + * + * with x=1.0: + * term2_aux(1, 1): 0.291213 + * term2_aux(2, 1): 1.50625e-5 + * term2_aux(3, 1): 4.02964e-14 + * term2_aux(4, 1): 5.57599e-27 + * + * with x=1.18: + * term2_aux(1, 1.18): 0.412292 + * term2_aux(2, 1.18): 3.44223e-4 + * term2_aux(3, 1.18): 2.39945e-10 + * term2_aux(4, 1.18): 1.39652e-19 + */ + static double term1_aux(uint32_t j, double x) { + double f = ::exp(-2.0 * x * x); + /* sgn: + * +1 for j in {2, 4, 6, ..} + * -1 for j in {1, 3, 5, ..} + */ + int32_t sgn = (((j & 0x1) == 0) ? +1 : -1); + + if(j == 1) { + return sgn * f; + } else { + return sgn * ::pow(f, j*j); + } + } /*term1_aux*/ + + /* implements P1(x) above; truncating at 4 terms. + * use .distr1() for x > ~1.18 + */ + static double distr1_impl(double x) { + double f = term1_aux(1.0, x); + double f2 = f*f; /*f^2*/ + double f4 = f2*f2; /*f^4*/ + double f8 = f4*f4; /*f^8*/ + double f9 = f8*f; /*f^9*/ + double f16 = f8*f8; /*f^16*/ + + double r = ((f16 + f9) + f4) + f; + + return 1.0 + 2.0*r; + } /*distr1_impl*/ + + /* pi: 3.141592... */ + static constexpr double c_pi = M_PI; + /* pi^2 / 8 */ + static constexpr double c_pi2_8 = 0.125 * c_pi * c_pi; + + /* computes + * + * (2j-1)^2 + * U(x) + * + * require: + * - j >= 1 + */ + static double term2_aux(uint32_t j, double x) { + double u = ::exp(-c_pi2_8 / (x * x)); + + if(j == 1) { + return u; + } else { + uint32_t j2m1 = 2*j - 1; + + return ::pow(u, j2m1 * j2m1); + } + } /*term2_aux*/ + + /* implements P2(x) above; truncating at 4 terms */ + static double distr2_impl(double x) { + static double c_sqrt_2pi + = ::sqrt(2.0 * c_pi); + + double scale = c_sqrt_2pi / x; + + double u = term2_aux(1, x); + double u2 = u*u; /*u^2*/ + double u4 = u2*u2; /*u^4*/ + double u8 = u4*u4; /*u^8*/ + double u16 = u8*u8; /*u^16*/ + double u32 = u16*u16; /*u^32*/ + + double u9 = u8*u; /*u^9*/ + double u25 = u16*u8*u; /*u^25*/ + double u49 = u32*u16*u; /*u^49*/ + + double r = ((u49 + u25) + u9) + u; + + return scale * r; + + } /*distr2_impl*/ + + static double distr_impl(double x) { + using xo::tostr; + + constexpr char const * c_self = "KolmogorovSmirnov::distr_impl"; + + if(x < 0.0) + throw std::runtime_error(tostr(c_self, "KS(x) cdf defined for x>=0")); + + if(x == 0.0) + return 0; + + if(x < 1.18) { + /* P2(x) converges fastest */ + return distr2_impl(x); + } else { + /* P1(x) converges fastest */ + return distr1_impl(x); + } + } /*distr_impl*/ + + /* p-value for significance of a particular value of the KS-statistic + * obtain this statistic for a sample distribution using + * Empirical.ks_stat_1sided(dexp) + * for some target distribution dexp + * + * ne. + * for 1-sided test: #of points in sample, i.e. Empirical.n_sample() + * for 2-sided test: (n1 . n2) / (n1 + n2) + * D. + * max difference between observed and expected cumulative distributions. + * see Empirical.ks_stat_1sided() + */ + static double ks_pvalue(uint32_t ne, double D) + { + using xo::scope; + using xo::xtag; + + constexpr bool logging_enabled_flag = false; + + scope log(XO_DEBUG(logging_enabled_flag)); + + double ne_sqrt = ::sqrt(ne); + + /* argument to KS-distribution. + * x in [0, +oo) + * + * A large value for x -> small value for 1 - KSdist(x), + * i.e. represents probability that a sample of size ne with a + * KS-statistic of magnitude D or larger, could be drawn from + * distribution dexp. + */ + double x = ne_sqrt + 0.12 + (0.11 / ne_sqrt); + + double xD = x * D; + + double pvalue = 1.0 - distr_impl(xD); + + log && log(xtag("ne", ne), + xtag("D", D), + xtag("ne_sqrt", ne_sqrt), + xtag("x", x), + xtag("xD", xD), + xtag("pvalue", pvalue)); + + return pvalue; + } /*ks_pvalue*/ + + /* cumulative distribution function */ + double distribution(double x) const { + return distr_impl(x); + } /*distribution*/ + + // ----- inherited from Distribution ----- + virtual double cdf(double const & x) const { + return this->distribution(x); + } /*cdf*/ + }; /*KolmogorovSmirnov*/ + } /*namespace distribution*/ +} /*namespace xo*/ + +/* end KolmogorovSmirnov.hpp */ diff --git a/include/xo/distribution/Normal.hpp b/include/xo/distribution/Normal.hpp new file mode 100644 index 00000000..5cef9ed1 --- /dev/null +++ b/include/xo/distribution/Normal.hpp @@ -0,0 +1,51 @@ +/* @file Normal.hpp */ + +#pragma once + +#include "distribution/Distribution.hpp" +#include + +namespace xo { + namespace distribution { + /* the guassian distribution, with mean 0 and variance 1 + */ + class Normal : public Distribution { + public: + Normal() = default; + + /* normal probability density: + * + * x^2 + * -(1/2) 1/2 + * p(x) = e / (2.pi) + */ + static double density(double x) { + static double c_sqrt_2pi = ::sqrt(2 * M_PI); + + return ::exp(-0.5 * x * x) / c_sqrt_2pi; + } /*density*/ + + /* cumulative distribution function for N(0,1): + * + * / x + * | + * | p(x).dx + * | + * / -oo + * + * where p(x) is the normal density function p(x) = e^[-x^2/2] + */ + static double cdf_impl(double x) { + return 0.5 * std::erfc(-M_SQRT1_2 * x); + } /*cdf_impl*/ + + // ----- inherited from Distribution ----- + + virtual double cdf(double const & x) const override { + return cdf_impl(x); + } /*cdf*/ + }; /*Normal*/ + } /*namespace distribution*/ +} /*namespace xo*/ + +/* end Normal.hpp */ diff --git a/include/xo/distribution/StdEmpirical.hpp b/include/xo/distribution/StdEmpirical.hpp new file mode 100644 index 00000000..d4475510 --- /dev/null +++ b/include/xo/distribution/StdEmpirical.hpp @@ -0,0 +1,226 @@ +/* @file StdEmpirical.hpp */ + +#pragma once + +#include "Empirical.hpp" +#include "xo/ordinaltree/RedBlackTree.hpp" +#include "xo/indentlog/scope.hpp" +#include +#include + +namespace xo { + namespace distribution { + /* an empirical distribution over a given domain + * (e.g. double as proxy for IR), + * obtained by sorting equally-weighted samples + */ + template + class StdEmpirical : public Distribution { + public: + using SampleMap = xo::tree::RedBlackTree>; + using const_iterator = typename SampleMap::const_iterator; + + public: + StdEmpirical() = default; + + uint32_t n_sample() const { return n_sample_; } + const_iterator begin() const { return sample_map_.begin(); } + const_iterator end() const { return sample_map_.end(); } + + /* compute kolmogorov-smirnov statistic with a non-sampled distribution. + * if d2 is sampled, should use .ks_stat_2sided() instead + */ + std::pair ks_stat_1sided(Distribution const & d2) const { + using xo::scope; + using xo::xtag; + + constexpr char const * c_self = "Empirical::ks_stat_1sided"; + constexpr bool c_logging_enabled = false; + + scope lscope(c_self, c_logging_enabled); + + double ks_stat = 0.0; + + /* for i'th loop iteration below: + * xj_sum = sum of all x[j] with j<=i + */ + uint32_t xj_sum = 0; + + /* #of sample in this distribution, as double */ + double nr = 1.0 / this->n_sample(); + + /* loop over elements x[i] of this (sampled) distribution, + * compare cdf(x[i]) with d2.cdf(x[i]) + * + * KS stat is the maximum observed difference. + */ + for(auto const & point : this->sample_map_) { + Domain const & xi = point.first; + uint32_t xi_count = point.second; + + xj_sum += xi_count; + + /* p1 = xi_sum / n1, where n1 = .n_sample() */ + double p1 = xj_sum * nr; + double p2 = d2.cdf(xi); + + double dp = std::abs(p1 - p2); + + if(c_logging_enabled) + lscope.log(c_self, + xtag("xi", xi), + xtag("xi_count", xi_count), + xtag("xj_sum", xj_sum), + xtag("p1", p1), + xtag("p2", p2), + xtag("dp", dp)); + + ks_stat = std::max(ks_stat, dp); + } + + return std::pair(this->n_sample(), ks_stat); + } /*ks_stat_1sided*/ + + /* compute kolmogorov-smirnov statistic with a sampled distribution; + * assess likelihood that both samples come from the same population. + */ + std::pair ks_stat_2sided(StdEmpirical const & d2) { + /* loop once over both sample distributions; + * algorithm is O(n1 + n2) for two empirical + * distributions with n1,n2 points respectively + */ + + /* return value observed here */ + double ks_stat = 0.0; + + auto ix1 = this->sample_map_.begin(); + auto end_ix1 = this->sample_map_.end(); + + auto ix2 = d2.sample_map_.begin(); + auto end_ix2 = this->sample_map_.end(); + + uint32_t xj1_sum = 0; + uint32_t xj2_sum = 0; + + uint32_t n1 = this->n_sample(); + uint32_t n2 = d2.n_sample(); + + double nr1 = 1.0 / n1; + double nr2 = 1.0 / n2; + + /* ^ + * 1| **. + * | ...*.. + * | . * + * | ********** + * | * . + * | ......... + * | . * + * | ******** + * | ..*..... + * +-----------------------> + * ^ ^ + * ix1 ix2 + */ + + while ((ix1 != end_ix1) || (ix2 != end_ix2)) { + /* on each iteration, compare sample distributions + * at smallest of (ix1->first, ix2->first) + */ + + bool advance_ix1_flag = false; + bool advance_ix2_flag = false; + + if (ix1 == end_ix1) { + /* only ix2 dereferenceable */ + advance_ix2_flag = true; + } else if (ix2 == end_ix2) { + /* only ix1 dereferenceable */ + advance_ix1_flag = true; + } else { + /* ix1,ix2 both dereferenceable */ + + advance_ix1_flag = (ix1->first <= ix2->first); + advance_ix2_flag = (ix2->first <= ix1->first); + } + + if (advance_ix1_flag) { + xj1_sum += ix1->first; + ++ix1; + } + if (advance_ix2_flag) { + xj2_sum += ix2->first; + ++ix2; + } + + double p1 = xj1_sum * nr1; + double p2 = xj2_sum * nr2; + + double dp = std::abs(p1 - p2); + + ks_stat = std::max(ks_stat, dp); + } + + /* ne = effective #of points to use when comparing 2 sample dist/s */ + double ne = (n1 * n2) / static_cast(n1 + n2); + + return std::pair(ne, ks_stat); + +#ifdef OBSOLETE + /* NOTE: this will cost O(n.log(n)) + * (for empirical distributions with n points) + * if we loop over both sets of points in parallel, + * can get O(n) solution + * + */ + + /* loop over points in *this */ + double ks1 = this->ks_stat_1sided(d2); + /* loop over points in d2 */ + double ks2 = d2.ks_stat_1sided(*this); + + return std::max(ks1, ks2); +#endif + } /*ks_stat_2sided*/ + + // ----- inherited from Empirical ----- + + /* introduce one new sample into this distribution */ + virtual void include_sample(Domain const & x) { + ++(this->n_sample_); + + /* note: xo::tree::RedBlackTree doesn't provide the usual reference result + * from operator[]; it needs to intervene after assignment to update + * order statistics + */ + auto lhs = this->sample_map_[x]; + + lhs += 1; + } /*include_sample*/ + + // ----- inherited from Distribution ----- + + virtual double cdf(Domain const & x) const override { + /* computes #of samples with values <= x */ + uint32_t nx = this->sample_map_.reduce_lub(x, true /*is_closed*/); + size_t n = this->n_sample(); + + return static_cast(nx) / n; + } /*cdf*/ + + private: + /* count #of calls to .include_sample() */ + CounterRep n_sample_ = 0; + + /* .sample_map_[x] counts the #of times + * .include_sample(x) has been called. + */ + SampleMap sample_map_; + }; /*StdEmpirical*/ + + } /*namespace distribution*/ +} /*namespace xo*/ + +/* end StdEmpirical.hpp */ diff --git a/include/xo/distribution/Uniform.hpp b/include/xo/distribution/Uniform.hpp new file mode 100644 index 00000000..d1bc8668 --- /dev/null +++ b/include/xo/distribution/Uniform.hpp @@ -0,0 +1,47 @@ +/* @file Uniform.hpp */ + +#pragma once + +#include "Distribution.hpp" + +namespace xo { + namespace distribution { + /* uniform distribution on an interval */ + class Uniform : public Distribution { + public: + Uniform(double lo, double hi) : lo_(lo), hi_(hi) {} + + static Uniform unit() { return Uniform(0.0, 1.0); } + + static double density_impl(double lo, double hi) { + return 1.0 / (hi - lo); + } /*density_impl*/ + + static double distr_impl(double lo, double hi, double x) { + return (x - lo) / (hi - lo); + } /*distr_impl*/ + + double lo() const { return lo_; } + double hi() const { return hi_; } + + double density(double /*x*/) const { + return density_impl(this->lo_, this->hi_); + } /*density*/ + + double distribution(double x) const { + return distr_impl(this->lo_, this->hi_, x); + } /*distribution*/ + + // ----- inherited from Distribution ----- + + virtual double cdf(double const & x) const override { + return this->distribution(x); + } /*cdf*/ + + private: + /* Invariant: .lo < .hi */ + double lo_; + double hi_; + }; /*Uniform*/ + } /*namespace distribution*/ +} /*namespace xo*/ From 797db3e021a702cb575ac06a788d121d44d6817d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 15:54:57 -0400 Subject: [PATCH 0392/2524] compile fixes + unit tests --- CMakeLists.txt | 4 ++-- cmake/xo_distributionConfig.cmake.in | 2 ++ include/xo/distribution/Normal.hpp | 5 ++++- include/xo/distribution/Uniform.hpp | 22 ++++++++++++++++------ src/distribution/CMakeLists.txt | 5 +++++ src/distribution/Normal.cpp | 9 +++++++++ utest/CMakeLists.txt | 19 +++++++++++++++++++ utest/Normal.test.cpp | 24 ++++++++++++++++++++++++ utest/Uniform.test.cpp | 27 +++++++++++++++++++++++++++ utest/distribution_utest_main.cpp | 6 ++++++ 10 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 src/distribution/CMakeLists.txt create mode 100644 src/distribution/Normal.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/Normal.test.cpp create mode 100644 utest/Uniform.test.cpp create mode 100644 utest/distribution_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 467f52cc..153acdac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,9 +10,9 @@ include(xo_macros/xo-project-macros) xo_cxx_toplevel_options() #add_subdirectory(example) -#add_subdirectory(uteest) +add_subdirectory(src/distribution) # note refcnt dep -> not header-only +add_subdirectory(utest) -xo_add_headeronly_library4(xo_distribution ${PROJECT_NAME}Targets) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- diff --git a/cmake/xo_distributionConfig.cmake.in b/cmake/xo_distributionConfig.cmake.in index 3c6c4bd4..d906d196 100644 --- a/cmake/xo_distributionConfig.cmake.in +++ b/cmake/xo_distributionConfig.cmake.in @@ -1,4 +1,6 @@ @PACKAGE_INIT@ +include(CMakeFindDependencyMacro) +find_dependency(refcnt) include("${CMAKE_CURRENT_LIST_DIR}/xo_distributionTargets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/include/xo/distribution/Normal.hpp b/include/xo/distribution/Normal.hpp index 5cef9ed1..6d4d88e1 100644 --- a/include/xo/distribution/Normal.hpp +++ b/include/xo/distribution/Normal.hpp @@ -2,7 +2,7 @@ #pragma once -#include "distribution/Distribution.hpp" +#include "Distribution.hpp" #include namespace xo { @@ -13,6 +13,9 @@ namespace xo { public: Normal() = default; + /* N(0,1): mean 0, sdev 1 */ + static ref::rp unit() { return new Normal(); } + /* normal probability density: * * x^2 diff --git a/include/xo/distribution/Uniform.hpp b/include/xo/distribution/Uniform.hpp index d1bc8668..e40c62a6 100644 --- a/include/xo/distribution/Uniform.hpp +++ b/include/xo/distribution/Uniform.hpp @@ -11,21 +11,31 @@ namespace xo { public: Uniform(double lo, double hi) : lo_(lo), hi_(hi) {} - static Uniform unit() { return Uniform(0.0, 1.0); } + static ref::rp unit() { return new Uniform(0.0, 1.0); } + + static double density_impl(double lo, double hi, double x) { + if (x <= lo) + return 0.0; + if (x >= hi) + return 0.0; - static double density_impl(double lo, double hi) { return 1.0 / (hi - lo); } /*density_impl*/ static double distr_impl(double lo, double hi, double x) { + if (x <= lo) + return 0.0; + if (x >= hi) + return 1.0; + return (x - lo) / (hi - lo); } /*distr_impl*/ double lo() const { return lo_; } double hi() const { return hi_; } - double density(double /*x*/) const { - return density_impl(this->lo_, this->hi_); + double density(double x) const { + return density_impl(this->lo_, this->hi_, x); } /*density*/ double distribution(double x) const { @@ -40,8 +50,8 @@ namespace xo { private: /* Invariant: .lo < .hi */ - double lo_; - double hi_; + double lo_ = 0.0; + double hi_ = 1.0; }; /*Uniform*/ } /*namespace distribution*/ } /*namespace xo*/ diff --git a/src/distribution/CMakeLists.txt b/src/distribution/CMakeLists.txt new file mode 100644 index 00000000..56ce881e --- /dev/null +++ b/src/distribution/CMakeLists.txt @@ -0,0 +1,5 @@ +set(SELF_LIB distribution) +set(SELF_SRCS Normal.cpp) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_dependency(${SELF_LIB} refcnt) diff --git a/src/distribution/Normal.cpp b/src/distribution/Normal.cpp new file mode 100644 index 00000000..f1a0f9ca --- /dev/null +++ b/src/distribution/Normal.cpp @@ -0,0 +1,9 @@ +/* @file Normal.cpp */ + +#include "Normal.hpp" + +namespace xo { + +} /*namespace xo*/ + +/* end Normal.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..27b1a78b --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,19 @@ +# build unittest distribution/utest + +set(SELF_EXE utest.distribution) +set(SELF_SRCS + distribution_utest_main.cpp + Normal.test.cpp + Uniform.test.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +target_code_coverage(${SELF_EXE} AUTO ALL) + +xo_self_dependency(${SELF_EXE} distribution) + +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# end CMakeLists.txt diff --git a/utest/Normal.test.cpp b/utest/Normal.test.cpp new file mode 100644 index 00000000..70f0a7ad --- /dev/null +++ b/utest/Normal.test.cpp @@ -0,0 +1,24 @@ +/* @file Normal.test.cpp */ + +#include "xo/distribution/Normal.hpp" +#include + +namespace xo { + using xo::distribution::Normal; + + namespace ut { + TEST_CASE("normal", "[distribution]") { + auto n01 = Normal::unit(); + + CHECK(n01->cdf(-3.0) == Approx(0.001349898).margin(1e-9)); + CHECK(n01->cdf(-2.0) == Approx(0.0227501319).margin(1e-9)); + CHECK(n01->cdf(-1.0) == Approx(0.1586552539).margin(1e-9)); + CHECK(n01->cdf(0.0) == 0.5); + CHECK(n01->cdf(1.0) == 1.0 - n01->cdf(-1.0)); + CHECK(n01->cdf(2.0) == 1.0 - n01->cdf(-2.0)); + CHECK(n01->cdf(3.0) == 1.0 - n01->cdf(-3.0)); + } /*TEST_CASE(normal)*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end Normal.test.cpp */ diff --git a/utest/Uniform.test.cpp b/utest/Uniform.test.cpp new file mode 100644 index 00000000..10153d66 --- /dev/null +++ b/utest/Uniform.test.cpp @@ -0,0 +1,27 @@ +/* @file Uniform.test.cpp */ + +#include "xo/distribution/Uniform.hpp" +#include + +namespace xo { + using xo::distribution::Uniform; + + namespace ut { + TEST_CASE("uniform", "[distribution]") { + auto u = Uniform::unit(); + + CHECK(u->cdf(-3.0) == 0.0); + CHECK(u->cdf(-2.0) == 0.0); + CHECK(u->cdf(-1.0) == 0.0); + CHECK(u->cdf(0.0) == 0.0); + CHECK(u->cdf(0.05) == 0.05); + CHECK(u->cdf(0.5) == 0.5); + CHECK(u->cdf(0.95) == 0.95); + CHECK(u->cdf(1.0) == 1.0); + CHECK(u->cdf(2.0) == 1.0); + CHECK(u->cdf(3.0) == 1.0); + } /*TEST_CASE(uniform)*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end Uniform.test.cpp */ diff --git a/utest/distribution_utest_main.cpp b/utest/distribution_utest_main.cpp new file mode 100644 index 00000000..b5e05a73 --- /dev/null +++ b/utest/distribution_utest_main.cpp @@ -0,0 +1,6 @@ +/* file distribution_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end distribution_utest_main.cpp */ From 7d599854fbc9f6052f5b353af1be5d56b7c8dc4c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 15:55:55 -0400 Subject: [PATCH 0393/2524] bugfix: bad include install in vanilla build --- cmake/xo_macros/xo_cxx.cmake | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 1e308aa0..06a28990 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -349,6 +349,8 @@ endmacro() macro(xo_establish_symlink_install) if(NOT DEFINED XO_SYMLINK_INSTALL) set(XO_SYMLINK_INSTALL False) + + message(XO_SYMLINK_INSTALL=${XO_SYMLINK_INSTALL}) endif() endmacro() @@ -387,14 +389,15 @@ endmacro() macro(xo_install_include_tree3 subdir_path) xo_establish_symlink_install() + # ugh. cmake doesn't allow input path argument to cmake_path() + # to be a macro variable. + set(_xo_install_include_tree3_subdir_path ${subdir_path}) + set(_xo_install_include_tree3_dirname "") + set(_xo_install_include_tree3_basename "") + cmake_path(GET _xo_install_include_tree3_subdir_path PARENT_PATH _xo_install_include_tree3_dirname) + cmake_path(GET _xo_install_include_tree3_subdir_path FILENAME _xo_install_include_tree3_basename) + if(XO_SYMLINK_INSTALL) - # ugh. cmake doesn't allow input path argument to cmake_path() - # to be a macro variable. - set(_xo_install_include_tree3_subdir_path ${subdir_path}) - set(_xo_install_include_tree3_dirname "") - set(_xo_install_include_tree3_basename "") - cmake_path(GET _xo_install_include_tree3_subdir_path PARENT_PATH _xo_install_include_tree3_dirname) - cmake_path(GET _xo_install_include_tree3_subdir_path FILENAME _xo_install_include_tree3_basename) xo_install_make_symlink( ${PROJECT_SOURCE_DIR}/${_xo_install_include_tree3_dirname} @@ -404,7 +407,7 @@ macro(xo_install_include_tree3 subdir_path) install( DIRECTORY ${PROJECT_SOURCE_DIR}/${subdir_path} FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ - DESTINATION ${CMAKE_INSTALL_PREFIX}/${subdir_path}) + DESTINATION ${CMAKE_INSTALL_PREFIX}/${_xo_install_include_tree3_dirname}) endif() endmacro() @@ -554,6 +557,7 @@ macro(xo_dependency_helper target visibility dep) xo_dependency_helper1(${target} ${visibility} repo/${_nxo_dep}/include) endif() else() + message("xo_dependency_helper: find_package() on ${dep} for ${target}") find_package(${dep} CONFIG REQUIRED) endif() From c7c29daf5e171fa8a95d9d38241aecf118a2689c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 20:16:01 -0400 Subject: [PATCH 0394/2524] initial implementation --- CMakeLists.txt | 13 ++++ README.md | 77 ++++++++++++++++++++++++ cmake/xo_pydistributionConfig.cmake.in | 4 ++ include/README.md | 1 + src/pydistribution/CMakeLists.txt | 7 +++ src/pydistribution/pydistribution.cpp | 74 +++++++++++++++++++++++ src/pydistribution/pydistribution.hpp.in | 25 ++++++++ 7 files changed, 201 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/xo_pydistributionConfig.cmake.in create mode 100644 include/README.md create mode 100644 src/pydistribution/CMakeLists.txt create mode 100644 src/pydistribution/pydistribution.cpp create mode 100644 src/pydistribution/pydistribution.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..d2823501 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +# xo-pydistribution/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pydistribution VERSION 1.0) + +include(xo_macros/xo-project-macros) + +xo_cxx_toplevel_options() + +add_subdirectory(src/pydistribution) + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/README.md b/README.md new file mode 100644 index 00000000..ef597486 --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# python bindings for c++ reflection library (xo-distribution) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-reflect](https://github.com/Rconybea/xo-distribution) + +### build + install +``` +$ cd xo-pydistribution +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +### build for unit test coverage +``` +$ cd xo-pydistribution +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pydistribution +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +## Examples + +Assumes `xo-pydistribution` installed to `~/local2/lib` + +``` +PYTHONPATH=~/local2/lib python +>>> import pydistribution +>>> dir(pydistribution) +['Distribution', 'ExplicitDist', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'normalcdf'] +>>> from pydistribution import * +``` + +normal distribution +``` +>>> normalcdf(0.0) +0.5 +>>> normalcdf(3.0) +0.9986501019683699 +``` + +explicit distribution (online implementation). +intended to model empirically a Bayesian prior. +``` +>>> d=ExplicitDist.make(bucket_dx=0.01, ref_value=1e-6) +>>> d +]"> +>>> d.cdf(0.0) +0.0 +>>> d.cdf(0.01) +1.0 +``` diff --git a/cmake/xo_pydistributionConfig.cmake.in b/cmake/xo_pydistributionConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pydistributionConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..ec349995 --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future pydistribution #include files diff --git a/src/pydistribution/CMakeLists.txt b/src/pydistribution/CMakeLists.txt new file mode 100644 index 00000000..a050be15 --- /dev/null +++ b/src/pydistribution/CMakeLists.txt @@ -0,0 +1,7 @@ +# xo_pydistribution/src/pydistribution/CMakeLists.txt + +set(SELF_LIB pydistribution) +set(SELF_SRCS pydistribution.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} xo_distribution) diff --git a/src/pydistribution/pydistribution.cpp b/src/pydistribution/pydistribution.cpp new file mode 100644 index 00000000..5d107233 --- /dev/null +++ b/src/pydistribution/pydistribution.cpp @@ -0,0 +1,74 @@ +/* @file pydistribution.cpp */ + +#include "pydistribution.hpp" +#include "xo/distribution/Normal.hpp" +#include "xo/distribution/ExplicitDist.hpp" +#include "xo/reflect/SelfTagging.hpp" +#include "xo/pyutil/pyutil.hpp" +#include +#include + +namespace xo { + using xo::distribution::Normal; + using xo::distribution::Distribution; + using xo::distribution::ExplicitDist; + using xo::ref::rp; + + namespace sim { + namespace py = pybind11; + + PYBIND11_MODULE(PYDISTRIBUTION_MODULE_NAME(), m) { + m.doc() = "pybind11 distribution plugin"; // optional module docstring + + m.def("normalcdf", + &Normal::cdf_impl, + "cumulative normal distribution", + py::arg("x")); + + py::class_, + rp>>(m, "Distribution") + .def("cdf", &Distribution::cdf, + "return cumulative distribution function at x", + py::arg("x")); + + py::class_, + Distribution, + rp>>(m, "ExplicitDist") + .def_static("make", &ExplicitDist::make, + "create instance", + py::arg("bucket_dx"), py::arg("ref_value")) + .def_static("make_n", &ExplicitDist::make_n, + "create instance with n buckets", + py::arg("n"), py::arg("bucket_dx"), py::arg("ref_value")) + .def("n_bucket", &ExplicitDist::n_bucket, + "return number of explicitly-represented buckets in distribution") + .def("lo", &ExplicitDist::lo, + "return least upper bound x: cdf(x)=0") + .def("hi", &ExplicitDist::hi, + "return greatest lower bound x: cdf(x)=1") + .def("density", &ExplicitDist::density, + "return probability density at x", + py::arg("x")) + .def("density_v", &ExplicitDist::density_v, + "return probability density vector for all explicit buckets." + " each member is pair {lh bucket edge, density}") + .def("signed_bucket_index", &ExplicitDist::signed_bucket_index, + "signed index to probability bucket. ref_value -> 0", + py::arg("x")) + .def("scale_bucket", &ExplicitDist::scale_bucket, + "scale probability weight in bucket containing x by k", + py::arg("x"), py::arg("k")) + .def("scale_by_normal_cdf", &ExplicitDist::scale_by_normal_cdf, + "scale by normal cumulative distribution N(sign.(x-mean)/sigma)." + " expect sign in {+1, -1}", + py::arg("sign"), py::arg("mean"), py::arg("sigma")) + .def("renormalize", &ExplicitDist::renormalize, + "renormalize to ensure sum of weights=1") + .def("check_renormalize", &ExplicitDist::check_renormalize, + "renormalize if needed, otherwise do nothing") + .def("__repr__", &ExplicitDist::display_string); + } + } /*namespace sim*/ +} /*namespace xo*/ + +/* end pydistribution.cpp */ diff --git a/src/pydistribution/pydistribution.hpp.in b/src/pydistribution/pydistribution.hpp.in new file mode 100644 index 00000000..bf94e785 --- /dev/null +++ b/src/pydistribution/pydistribution.hpp.in @@ -0,0 +1,25 @@ +/* @file pydistribution.hpp + * + * automatically generated from src/pydistribution/pydistribution.hpp.in + * see src/pydistribution/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYDISTRIBUTION_MODULE_NAME(), m) { ... } + */ +#define PYDISTRIBUTION_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(PYDISTRIBUTION_MODULE_NAME_STR) + */ +#define PYDISTRIBUTION_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * PYDISTRIBUTION_IMPORT_MODULE() + * replaces + * py::module_::import("pydistribution") + */ +#define PYDISTRIBUTION_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pydistribution.hpp */ From e3743cc01feb9daa8b9c0481f1d6939a0a5829df Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 20:18:45 -0400 Subject: [PATCH 0395/2524] + bernoulli/exponential/uniform/guussianpair --- include/xo/randomgen/bernoulligen.hpp | 29 ++++++ include/xo/randomgen/exponentialgen.hpp | 21 +++++ include/xo/randomgen/gaussianpairgen.hpp | 108 +++++++++++++++++++++++ include/xo/randomgen/uniformgen.hpp | 25 ++++++ 4 files changed, 183 insertions(+) create mode 100644 include/xo/randomgen/bernoulligen.hpp create mode 100644 include/xo/randomgen/exponentialgen.hpp create mode 100644 include/xo/randomgen/gaussianpairgen.hpp create mode 100644 include/xo/randomgen/uniformgen.hpp diff --git a/include/xo/randomgen/bernoulligen.hpp b/include/xo/randomgen/bernoulligen.hpp new file mode 100644 index 00000000..9de59300 --- /dev/null +++ b/include/xo/randomgen/bernoulligen.hpp @@ -0,0 +1,29 @@ +/* @file bernoulligen.hpp */ + +#pragma once + +#include "generator.hpp" +#include + +namespace xo { + namespace rng { + /* Engine: e.g. xo::rng::xoshiro256ss or std::mt19937 */ + template + class bernoulligen : public generator> { + public: + using generator_type = generator>; + + template + static generator_type make(Engine engine, double prob) { + return generator_type::make(std::move(engine), + std::bernoulli_distribution(prob)); + } + + template + static generator_type conflip(Engine engine) { + return generator_type::make(std::move(engine), + std::bernoulli_distribution(0.5)); + } + }; + } /*namespace rng*/ + } /*namespace xo*/ diff --git a/include/xo/randomgen/exponentialgen.hpp b/include/xo/randomgen/exponentialgen.hpp new file mode 100644 index 00000000..35945f5b --- /dev/null +++ b/include/xo/randomgen/exponentialgen.hpp @@ -0,0 +1,21 @@ +/* @file exponentialgen.hpp */ + +#pragma once + +#include "generator.hpp" +#include + +namespace xo { + namespace rng { + template + class exponentialgen : public generator> { + public: + using generator_type = generator>; + + template + static generator_type make(Engine eng, double lambda) { + return make_generator(std::move(eng), std::exponential_distribution(lambda)); + } + }; + } /*namespace rng*/ +} /*namespace xo*/ diff --git a/include/xo/randomgen/gaussianpairgen.hpp b/include/xo/randomgen/gaussianpairgen.hpp new file mode 100644 index 00000000..d5cc4699 --- /dev/null +++ b/include/xo/randomgen/gaussianpairgen.hpp @@ -0,0 +1,108 @@ +/* @file gaussianpairgen.hpp */ + +#pragma once + +#include "generator.hpp" +#include +#include + +namespace xo { + namespace random { + /* editor bait: 2d normal, normal xy + * + * if + * N1 ~ N(0,1) + * N2 ~ N(0,1) + * are two indepenent, normally-distributed r.v's with + * mean 0 and variance 1, then + * let + * A = | 1 0 | X = | N1 | + * | r q | | N2 | + * + * with r^2 + q^2 = 1 + * + * and consider + * A.X = | N1 | := | Y1 | + * | r.N1 + q.N2 | | Y2 | + * + * Y1, Y2 both have mean 0, + * since both are linear combination of 0-mean N(0,1) variables + * + * Var(Y1) = 1 + * Var(Y2) = r^2.Var(N1) + q^2.Var(N2) + * = r^2 + q^2 + * = 1 + * + * (since N1,N2 indept, and Var(N1)=Var(N2)=1) + * + * Cov(Y1,Y2) = r.Cov(N1,N1) + q.Cov(N1,N2) + * = r.Var(N1) + * = r + * + * (since Cov(N1,N2)=0) + * + * we have correlation coefficient for Y1,Y2: + * + * Cov(Y1,Y2) + * p(Y1,Y2) = -------------------- + * sqrt(Var(Y1).Var(Y2)) + * + * = r + */ + template + class gaussianpair_dist { + public: + using result_type = std::array; + + public: + /* generate pairs of gaussian N(0,1) random numbers, + * with correlation coefficient rho + * + * Require: + * - rho in the interval [-1, +1] + */ + explicit gaussianpair_dist(FloatType rho) + : r_(rho), q_(std::sqrt(1.0 - rho*rho)) {} + + template + result_type operator()(Engine & engine) { + FloatType n1 = this->ndist_(engine); + FloatType n2 = this->ndist_(engine); + + FloatType y1 = n1; + FloatType y2 = this->r_ * n1 + this->q_ * n2; + + return {y1, y2}; + } /*operator()*/ + + private: + /* correlation coefficient r + * 2nd random variable Y2 in each pair will be constructed by + * r.N1 + sqrt(1-r^2).N2 + */ + FloatType r_; + /* q := sqrt(1-r^2) */ + FloatType q_; + + /* state for generating indept normally-distributed r.v's */ + std::normal_distribution ndist_; + }; /*gaussianpair_dist*/ + + /* generate pairs of correlated gaussian random variables */ + template + class gaussianpairgen { + public: + using engine_type = Engine; + using generator_type = generator>; + + template + static generator_type make(Engine eng, + double rho) { + return generator_type::make(std::move(eng), + gaussianpair_dist(rho)); + } + }; /*GaussianPairGen*/ + } /*namespace random*/ +} /*namespace xo*/ + +/* end gaussianpairgen.hpp */ diff --git a/include/xo/randomgen/uniformgen.hpp b/include/xo/randomgen/uniformgen.hpp new file mode 100644 index 00000000..d1c4d62b --- /dev/null +++ b/include/xo/randomgen/uniformgen.hpp @@ -0,0 +1,25 @@ +/* @file uniformgen.hpp */ + +#pragma once + +#include "generator.hpp" +#include + +namespace xo { + namespace rng { + template + class uniformgen : public generator> { + public: + using generator_type = generator>; + + template + static generator_type unit(Engine eng) { + return make_generator(std::move(eng), + std::uniform_real_distribution(0.0, 1.0)); + } + }; + } /*namespace rng*/ +} /*namespace xo*/ + + +/* end uniformgen.hpp */ From 3d2cdc159bf5e1116993497875abebd280bb89df Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 20:21:22 -0400 Subject: [PATCH 0396/2524] library name distribution -> xo_distribution --- src/distribution/CMakeLists.txt | 2 +- utest/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/distribution/CMakeLists.txt b/src/distribution/CMakeLists.txt index 56ce881e..41023695 100644 --- a/src/distribution/CMakeLists.txt +++ b/src/distribution/CMakeLists.txt @@ -1,4 +1,4 @@ -set(SELF_LIB distribution) +set(SELF_LIB xo_distribution) set(SELF_SRCS Normal.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 27b1a78b..652bf644 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -12,7 +12,7 @@ xo_include_options2(${SELF_EXE}) add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) target_code_coverage(${SELF_EXE} AUTO ALL) -xo_self_dependency(${SELF_EXE} distribution) +xo_self_dependency(${SELF_EXE} xo_distribution) xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) From 86b97d238f46e1390537a22f4a3a3cbfecef2848 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 20:27:14 -0400 Subject: [PATCH 0397/2524] bugfix: include structure missing xo/ subdir --- include/{ => xo}/statistics/Accumulator.hpp | 0 include/{ => xo}/statistics/Histogram.hpp | 0 include/{ => xo}/statistics/SampleStatistics.hpp | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename include/{ => xo}/statistics/Accumulator.hpp (100%) rename include/{ => xo}/statistics/Histogram.hpp (100%) rename include/{ => xo}/statistics/SampleStatistics.hpp (100%) diff --git a/include/statistics/Accumulator.hpp b/include/xo/statistics/Accumulator.hpp similarity index 100% rename from include/statistics/Accumulator.hpp rename to include/xo/statistics/Accumulator.hpp diff --git a/include/statistics/Histogram.hpp b/include/xo/statistics/Histogram.hpp similarity index 100% rename from include/statistics/Histogram.hpp rename to include/xo/statistics/Histogram.hpp diff --git a/include/statistics/SampleStatistics.hpp b/include/xo/statistics/SampleStatistics.hpp similarity index 100% rename from include/statistics/SampleStatistics.hpp rename to include/xo/statistics/SampleStatistics.hpp From 026560c4272b7794de73451b1bf350dd04f1ac4b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 20:28:52 -0400 Subject: [PATCH 0398/2524] bugfix: compile fixes --- src/pydistribution/CMakeLists.txt | 1 + src/pydistribution/pydistribution.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pydistribution/CMakeLists.txt b/src/pydistribution/CMakeLists.txt index a050be15..8adaad7e 100644 --- a/src/pydistribution/CMakeLists.txt +++ b/src/pydistribution/CMakeLists.txt @@ -5,3 +5,4 @@ set(SELF_SRCS pydistribution.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} xo_distribution) +xo_pybind11_dependency(${SELF_LIB} xo_pyutil) diff --git a/src/pydistribution/pydistribution.cpp b/src/pydistribution/pydistribution.cpp index 5d107233..8104ccaf 100644 --- a/src/pydistribution/pydistribution.cpp +++ b/src/pydistribution/pydistribution.cpp @@ -3,7 +3,7 @@ #include "pydistribution.hpp" #include "xo/distribution/Normal.hpp" #include "xo/distribution/ExplicitDist.hpp" -#include "xo/reflect/SelfTagging.hpp" +//#include "xo/reflect/SelfTagging.hpp" #include "xo/pyutil/pyutil.hpp" #include #include From d7a884f651d0d57d0e099149f68339f8711a6caa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 23 Oct 2023 20:29:14 -0400 Subject: [PATCH 0399/2524] bugfix: compile fix (include path ) --- utest/KalmanFilter.test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/KalmanFilter.test.cpp b/utest/KalmanFilter.test.cpp index c0b35601..fea572ec 100644 --- a/utest/KalmanFilter.test.cpp +++ b/utest/KalmanFilter.test.cpp @@ -3,7 +3,7 @@ #include "xo/kalmanfilter/KalmanFilter.hpp" #include "xo/kalmanfilter/KalmanFilterEngine.hpp" #include "xo/kalmanfilter/print_eigen.hpp" -#include "statistics/SampleStatistics.hpp" +#include "xo/statistics/SampleStatistics.hpp" #include "xo/randomgen/normalgen.hpp" #include "xo/randomgen/xoshiro256.hpp" #include "xo/indentlog/scope.hpp" From 471a0d4d43864e0f474e49b9fc9e93e943689c6e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 11:13:48 -0400 Subject: [PATCH 0400/2524] initial implementation --- CMakeLists.txt | 13 ++ cmake/xo_pykalmanfilterConfig.cmake.in | 4 + include/README.md | 1 + src/pykalmanfilter/CMakeLists.txt | 33 +++ src/pykalmanfilter/pykalmanfilter.cpp | 286 +++++++++++++++++++++++ src/pykalmanfilter/pykalmanfilter.hpp.in | 25 ++ 6 files changed, 362 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/xo_pykalmanfilterConfig.cmake.in create mode 100644 include/README.md create mode 100644 src/pykalmanfilter/CMakeLists.txt create mode 100644 src/pykalmanfilter/pykalmanfilter.cpp create mode 100644 src/pykalmanfilter/pykalmanfilter.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..2a1602f6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +# xo-pykalmanfilter/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pykalmanfilter VERSION 1.0) + +include(xo_macros/xo-project-macros) + +xo_cxx_toplevel_options() + +add_subdirectory(src/pykalmanfilter) + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/cmake/xo_pykalmanfilterConfig.cmake.in b/cmake/xo_pykalmanfilterConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pykalmanfilterConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..4454f162 --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future pyreflect #include files diff --git a/src/pykalmanfilter/CMakeLists.txt b/src/pykalmanfilter/CMakeLists.txt new file mode 100644 index 00000000..9fd9178c --- /dev/null +++ b/src/pykalmanfilter/CMakeLists.txt @@ -0,0 +1,33 @@ +# xo_pykalmanfilter/src/pykalmanfilter/CMakeLists.txt + +set(SELF_LIB pykalmanfilter) +set(SELF_SRCS pykalmanfilter.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} xo_kalmanfilter) +xo_pybind11_dependency(${SELF_LIB} xo_pyutil) + +# cmake config generated by xo-kalman filter +# (xo-kalmanfilter/cmake/xo_kalmanfilterConfig.cmake) +# doesn't work here. +# kalmanfilterConfig.cmake include path that would make +# #include +# work; we want to be able to use +# #include +# +# BTW this tells us that there's some discrepancy between what +# cmake does in response to +# +# (A) xo_kalmanfilterTarget.cmake: +# set_target_properties(xo_kalmanfilter PROPERTIES +# .. +# INTERFACE_LINK_LIBRARIES "reactor;Eigen3::Eigen") +# +# (B) target_link_libraries() below; via the original +# xo_external_target_dependency +# (xo_kalmanfilter Eigen3 Eigen3::Eigen) +# in xo-kalmanfilter/src/kalmanfilter/CMakeLists.txt +# +target_link_libraries(${SELF_LIB} PUBLIC Eigen3::Eigen) + +# CMakeLists.txt diff --git a/src/pykalmanfilter/pykalmanfilter.cpp b/src/pykalmanfilter/pykalmanfilter.cpp new file mode 100644 index 00000000..1f7320ce --- /dev/null +++ b/src/pykalmanfilter/pykalmanfilter.cpp @@ -0,0 +1,286 @@ +/* @file pykalmanfilter.cpp */ + +#include "pykalmanfilter.hpp" +#include "xo/pyreactor/pyreactor.hpp" +#include "xo/pyutil/pyutil.hpp" + +#include "xo/kalmanfilter/init_filter.hpp" +#include "xo/refcnt/Refcounted.hpp" +#include "xo/kalmanfilter/KalmanFilterSvc.hpp" +#include "xo/kalmanfilter/KalmanFilter.hpp" +#include "xo/kalmanfilter/KalmanFilterEngine.hpp" +#include "xo/kalmanfilter/KalmanFilterSpec.hpp" +#include "xo/kalmanfilter/KalmanFilterStep.hpp" +#include "xo/kalmanfilter/KalmanFilterStateToConsole.hpp" +#include "xo/kalmanfilter/KalmanFilterState.hpp" +#include "xo/kalmanfilter/KalmanFilterTransition.hpp" +#include "xo/kalmanfilter/KalmanFilterObservable.hpp" +#include "xo/kalmanfilter/KalmanFilterInputToConsole.hpp" +#include "xo/kalmanfilter/KalmanFilterInput.hpp" +#include "xo/reactor/EventStore.hpp" +#include "xo/subsys/Subsystem.hpp" + +#include +#include +#include +#include +#include +//#include + +namespace xo { + using xo::kalman::KalmanFilterSvc; + using xo::kalman::KalmanFilter; + using xo::kalman::KalmanFilterState; + using xo::kalman::KalmanFilterEngine; + using xo::kalman::KalmanFilterSpec; + using xo::kalman::KalmanFilterStepBase; + using xo::kalman::KalmanFilterStep; + using xo::kalman::KalmanFilterStateToConsole; + using xo::kalman::KalmanFilterState; + using xo::kalman::KalmanFilterStateExt; + using xo::kalman::KalmanFilterTransition; + using xo::kalman::KalmanFilterObservable; + using xo::kalman::KalmanFilterInputToConsole; + using xo::kalman::KalmanFilterInput; + using xo::reactor::AbstractSource; + using xo::reactor::AbstractSink; + using xo::reactor::AbstractEventStore; + using xo::reactor::ReactorSource; + using xo::reactor::PtrEventStore; + using xo::reflect::SelfTagging; + using xo::ref::rp; + using xo::time::utc_nanos; + using Eigen::VectorXd; + using Eigen::VectorXi; + using Eigen::MatrixXd; + namespace py = pybind11; + + namespace filter { + PYBIND11_MODULE(PYKALMANFILTER_MODULE_NAME(), m) { + /* ensure filter/ will be initialized */ + InitSubsys::require(); + /* ..and immediately perform init steps + * (this 2nd step is sketchy -- want to pass an application context + * to initialize_all()) + */ + Subsystem::initialize_all(); + + /* e.g. need python wrapper for for xo::reactor::AbstractSource + */ + PYREACTOR_IMPORT_MODULE(); + + m.doc() = "pybind11 plugin for xo.filter"; + + m.def("print_matrix", + [](MatrixXd const & m) { + std::cout << m << std::endl; + }); + + m.def("print_vector", + [](VectorXd const & v) { + std::cout << v << std::endl; + }); + + // ----- xo::kalman::KalmanFilterState ----- + + py::class_>(m, "KalmanFilterState") + .def_static("make", + py::overload_cast(&KalmanFilterState::make), + py::arg("k"), py::arg("tk"), + py::arg("x"), py::arg("P"), + py::arg("transition")) + .def("step_no", &KalmanFilterState::step_no) + .def("tm", &KalmanFilterState::tm) + .def("n_state", &KalmanFilterState::n_state) + .def("state_v", &KalmanFilterState::state_v) + .def("state_cov", &KalmanFilterState::state_cov) + .def_property_readonly("k", &KalmanFilterState::step_no) + .def_property_readonly("tk", &KalmanFilterState::tm) + .def_property_readonly("x", &KalmanFilterState::state_v) + .def_property_readonly("P", &KalmanFilterState::state_cov) + .def("__repr__", &KalmanFilterState::display_string); + + // ----- xo::kalman::KalmanFilterStateExt ----- + + py::class_>(m, "KalmanFilterStateExt") + .def_static("make", + py::overload_cast>(&KalmanFilterStateExt::make), + py::arg("k"), + py::arg("tk"), + py::arg("x"), + py::arg("P"), + py::arg("transition"), + py::arg("K"), + py::arg("j"), + py::arg("zk")) + .def_property_readonly("j", &KalmanFilterStateExt::observable) + .def_property_readonly("K", &KalmanFilterStateExt::gain) + .def_property_readonly("zk", &KalmanFilterStateExt::zk); + + // ----- xo::kalman::KalmanFilterTransition ----- + + py::class_(m, "KalmanFilterTransition") + .def(py::init()) + .def("n_state", &KalmanFilterTransition::n_state) + .def("transition_mat", &KalmanFilterTransition::transition_mat) + .def("transition_cov", &KalmanFilterTransition::transition_cov) + .def("check_ok", &KalmanFilterTransition::check_ok) + .def_property_readonly("F", &KalmanFilterTransition::transition_mat) + .def_property_readonly("Q", &KalmanFilterTransition::transition_cov) + .def("__repr__", &KalmanFilterTransition::display_string); + + // ----- xo::kalman::KalmanFilterObservable ----- + + py::class_(m, "KalmanFilterObservable") + .def(py::init(), + py::arg("keep"), py::arg("H"), py::arg("R")) + .def("n_state", &KalmanFilterObservable::n_state) + .def("n_observable", &KalmanFilterObservable::n_observable) + .def("observable_mat", &KalmanFilterObservable::observable) + .def("observable_cov", &KalmanFilterObservable::observable_cov) + .def_property_readonly("H", &KalmanFilterObservable::observable) + .def_property_readonly("R", &KalmanFilterObservable::observable_cov) + .def("__repr__", &KalmanFilterObservable::display_string); + + // ----- xo::kalman::KalmanFilterInput ----- + + py::class_>(m, "KalmanFilterInput") + //.def(py::init(), + // py::arg("tkp1"), py::arg("z")) + .def("n_observable", &KalmanFilterInput::n_obs) + .def_property_readonly("tkp1", &KalmanFilterInput::tkp1) + .def_property_readonly("z", &KalmanFilterInput::z) + .def("__repr__", &KalmanFilterInput::display_string); + + m.def("make_kalman_filter_input", &KalmanFilterInput::make, + py::arg("tkp1"), py::arg("presence"), py::arg("z"), py::arg("zerr")); + + // ----- xo::kalman::KalmanFilterStep ----- + + py::class_(m, "KalmanFilterStep") + .def(py::init, KalmanFilterTransition, KalmanFilterObservable, ref::rp>(), + py::arg("state"), py::arg("model"), py::arg("obs"), py::arg("input")) + .def_property_readonly("state", &KalmanFilterStep::state) + .def_property_readonly("model", &KalmanFilterStepBase::model) + .def_property_readonly("obs", &KalmanFilterStepBase::obs) + .def_property_readonly("input", &KalmanFilterStep::input) + .def("extrapolate", &KalmanFilterStep::extrapolate) + .def("gain", &KalmanFilterStep::gain) + .def("gain1", &KalmanFilterStep::gain1) + .def("correct", &KalmanFilterStep::correct) + .def("correct1", &KalmanFilterStep::correct1) + .def("__repr__", &KalmanFilterStep::display_string); + + // ----- xo::kalman::KalmanFilterSpec ----- + + py::class_(m, "KalmanFilterSpec") + .def(py::init, KalmanFilterSpec::MkStepFn>(), + py::arg("s0"), py::arg("mkstepfn")) + .def("start_ext", &KalmanFilterSpec::start_ext) + .def("make_step", &KalmanFilterSpec::make_step, + py::arg("sk"), py::arg("zkp1")) + .def("__repr__", &KalmanFilterSpec::display_string); + + // ----- xo::kalman::KalmanFilterEngine ----- + + m.def("kf_engine_extrapolate", + &KalmanFilterEngine::extrapolate); + m.def("kf_engine_gain", + &KalmanFilterEngine::kalman_gain); + m.def("kf_engine_gain1", + &KalmanFilterEngine::kalman_gain1); + m.def("kf_engine_correct", + &KalmanFilterEngine::correct); + m.def("kf_engine_correct1", + &KalmanFilterEngine::correct1); + + // ----- xo::kalman::KalmanFilter ----- + + py::class_(m, "KalmanFilter") + .def(py::init(), + py::arg("spec")) + .def_property_readonly("step_no", &KalmanFilter::step_no) + .def_property_readonly("tm", &KalmanFilter::tm) + .def_property_readonly("filter_spec", &KalmanFilter::filter_spec) + .def_property_readonly("step", &KalmanFilter::step) + .def_property_readonly("state_ext", &KalmanFilter::state_ext) + .def("notify_input", &KalmanFilter::notify_input) + .def("__repr__", &KalmanFilter::display_string); + + // ----- xo::kalman::KalmanFilterSvc ----- + + py::class_>(m, "KalmanFilterSvc") + .def_property_readonly("filter", &KalmanFilterSvc::filter) + .def_property_readonly("last_annexed_ev", &KalmanFilterSvc::last_annexed_ev); + + m.def("make_kalman_filter", + &KalmanFilterSvc::make, + py::arg("spec")); + + // ----- xo::kalman::KalmanFilterInputToConsole ----- + + py::class_> + (m, "KalmanFilterInputToConsole"); + + /* prints KalmanFilterInput events to console */ + m.def("make_kalman_filter_input_printer", &KalmanFilterInputToConsole::make); + + // ----- xo::kalman::KalmanFilterStateToConsole ----- + + py::class_> + (m, "KalmanFilterStateToConsole"); + + /* prints KalmanFilterStateExt events to console */ + m.def("make_kalman_filter_state_printer", &KalmanFilterStateToConsole::make); + + // ----- xo::kalman::KalmanFilterStateStore ----- + + using KalmanFilterStateEventStore + = PtrEventStore>; + + /* see also: UpxEventStore in [pyprocess/pyprocess.cpp] + * BboTickStore in [pyoption/pyoption.cpp] + */ + py::class_> + (m, "KalmanFilterStateEventStore") + .def_static("make", &KalmanFilterStateEventStore::make) + .def("last_n", &KalmanFilterStateEventStore::last_n, py::arg("n")) + .def("last_dt", &KalmanFilterStateEventStore::last_dt, py::arg("dt")); + //.def("__repr__", &KalmanFilterStateEventStore::display_string); + + +#ifdef OBSOLETE + // ----- xo::option::Pxtick ----- + + py::enum_(m, "Pxtick") + .value("nickel_dime", Pxtick::nickel_dime); + //.export_values(); // only need this for pre-c++11-style enum inside a class + + // ----- xo::option::OptionStrikeSet ----- + + py::class_>(m, "OptionStrikeSet") + .def("get_options", + [](OptionStrikeSet const & x) { + std::vector> v; + x.append_options(&v); + return v; + }) +#endif + } /*filter_py*/ + } /*namespace filter*/ +} /*namespace xo*/ + +/* end pykalmanfilter.cpp */ diff --git a/src/pykalmanfilter/pykalmanfilter.hpp.in b/src/pykalmanfilter/pykalmanfilter.hpp.in new file mode 100644 index 00000000..f2b1207d --- /dev/null +++ b/src/pykalmanfilter/pykalmanfilter.hpp.in @@ -0,0 +1,25 @@ +/* @file pykalmanfilter.hpp + * + * automatically generated from src/pykalmanfilter/pykalmanfilter.hpp.in + * see src/pykalmanfilter/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYKALMANFILTER_MODULE_NAME(), m) { ... } + */ +#define PYKALMANFILTER_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(PYKALMANFILTER_MODULE_NAME_STR) + */ +#define PYKALMANFILTER_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * PYKALMANFILTER_IMPORT_MODULE() + * replaces + * py::module_::import("pykalmanfilter") + */ +#define PYKALMANFILTER_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pykalmanfilter.hpp */ From 098a208ee0299197197e756b23de5ddf4c76d9fe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 11:42:19 -0400 Subject: [PATCH 0401/2524] bugfix: + pyreactor dep + eigen3 dep for submodule build --- src/pykalmanfilter/CMakeLists.txt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/pykalmanfilter/CMakeLists.txt b/src/pykalmanfilter/CMakeLists.txt index 9fd9178c..f551c368 100644 --- a/src/pykalmanfilter/CMakeLists.txt +++ b/src/pykalmanfilter/CMakeLists.txt @@ -5,6 +5,7 @@ set(SELF_SRCS pykalmanfilter.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} xo_kalmanfilter) +xo_pybind11_header_dependency(${SELF_LIB} pyreactor) xo_pybind11_dependency(${SELF_LIB} xo_pyutil) # cmake config generated by xo-kalman filter @@ -23,11 +24,14 @@ xo_pybind11_dependency(${SELF_LIB} xo_pyutil) # .. # INTERFACE_LINK_LIBRARIES "reactor;Eigen3::Eigen") # -# (B) target_link_libraries() below; via the original -# xo_external_target_dependency +# (B) xo_external_target_dependency # (xo_kalmanfilter Eigen3 Eigen3::Eigen) -# in xo-kalmanfilter/src/kalmanfilter/CMakeLists.txt +# below # -target_link_libraries(${SELF_LIB} PUBLIC Eigen3::Eigen) +# reminder XO_SUBMODULE_BUILD relies on +# xo_external_target_dependency() calling find_package() +# +xo_external_target_dependency(${SELF_LIB} Eigen3 Eigen3::Eigen) +#target_link_libraries(${SELF_LIB} PUBLIC Eigen3::Eigen) # CMakeLists.txt From 361a957e3c263dcec19011c36fb12d18f4b245f4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 12:24:03 -0400 Subject: [PATCH 0402/2524] + .hpp deps pyreactor,pywebutil --- src/pyprocess/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pyprocess/CMakeLists.txt b/src/pyprocess/CMakeLists.txt index 1b6c5347..03237164 100644 --- a/src/pyprocess/CMakeLists.txt +++ b/src/pyprocess/CMakeLists.txt @@ -6,3 +6,5 @@ set(SELF_SRCS pyprocess.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} process) +xo_pybind11_header_dependency(${SELF_LIB} pyreactor) +xo_pybind11_header_dependency(${SELF_LIB} pywebutil) From 38c95a554238825e314a46ffdbedf985592f5aab Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 12:26:21 -0400 Subject: [PATCH 0403/2524] build: drop XO_SYMLINK_INSTALL console message --- cmake/xo_macros/xo_cxx.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 06a28990..08d0b942 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -350,7 +350,7 @@ macro(xo_establish_symlink_install) if(NOT DEFINED XO_SYMLINK_INSTALL) set(XO_SYMLINK_INSTALL False) - message(XO_SYMLINK_INSTALL=${XO_SYMLINK_INSTALL}) + #message(XO_SYMLINK_INSTALL=${XO_SYMLINK_INSTALL}) endif() endmacro() From fa93e06bd5af2cbb28140eeda222945ddb36132a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 13:11:04 -0400 Subject: [PATCH 0404/2524] get build working --- CMakeLists.txt | 13 +++++++ cmake/xo_pywebsockConfig.cmake.in | 4 ++ include/README.md | 1 + src/pywebsock/CMakeLists.txt | 9 +++++ src/pywebsock/pywebsock.cpp | 63 +++++++++++++++++++++++++++++++ src/pywebsock/pywebsock.hpp.in | 25 ++++++++++++ 6 files changed, 115 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/xo_pywebsockConfig.cmake.in create mode 100644 include/README.md create mode 100644 src/pywebsock/CMakeLists.txt create mode 100644 src/pywebsock/pywebsock.cpp create mode 100644 src/pywebsock/pywebsock.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..20774e1c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +# xo-pywebsock/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pywebsock VERSION 1.0) + +include(xo_macros/xo-project-macros) + +xo_cxx_toplevel_options() + +add_subdirectory(src/pywebsock) + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/cmake/xo_pywebsockConfig.cmake.in b/cmake/xo_pywebsockConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pywebsockConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..1c13f62a --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder. needed at install for generated .hpp file diff --git a/src/pywebsock/CMakeLists.txt b/src/pywebsock/CMakeLists.txt new file mode 100644 index 00000000..0ad6f596 --- /dev/null +++ b/src/pywebsock/CMakeLists.txt @@ -0,0 +1,9 @@ +# xo_pywebsock/src/pywebsock/CMakeLists.txt + +set(SELF_LIB pywebsock) +set(SELF_SRCS pywebsock.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} websock) +#xo_pybind11_header_dependency(${SELF_LIB pyfoo) +xo_pybind11_dependency(${SELF_LIB} xo_pyutil) diff --git a/src/pywebsock/pywebsock.cpp b/src/pywebsock/pywebsock.cpp new file mode 100644 index 00000000..e92c43ee --- /dev/null +++ b/src/pywebsock/pywebsock.cpp @@ -0,0 +1,63 @@ +/* @file pywebsock.cpp */ + +#include "pywebsock.hpp" +#include "xo/pywebutil/pywebutil.hpp" +#include "xo/websock/Webserver.hpp" +#include "xo/printjson/PrintJson.hpp" +//#include "web_util/EndpointDescr.hpp" +#include "xo/pyutil/pyutil.hpp" +#include + +namespace xo { + using xo::web::WebserverConfig; + using xo::web::Webserver; + using xo::web::Runstate; + using xo::json::PrintJsonSingleton; + using xo::ref::rp; + namespace py = pybind11; + + namespace web { + PYBIND11_MODULE(PYWEBSOCK_MODULE_NAME(), m) { + PYWEBUTIL_IMPORT_MODULE(); // = py::module_::import("pywebutil") + + /* module docstring */ + m.doc() = "pybind11 plugin for xo.websock"; + + py::enum_(m, "Runstate") + .value("stopped", Runstate::stopped) + .value("stop_requested", Runstate::stop_requested) + .value("running", Runstate::running); + + py::class_(m, "WebserverConfig") + .def(py::init(), + py::arg("port"), + py::arg("tls_flag"), + py::arg("host_check_flag"), + py::arg("use_retry_flag")) + .def_property_readonly("port", &WebserverConfig::port) + .def_property_readonly("tls_flag", &WebserverConfig::tls_flag) + .def_property_readonly("host_check_flag", &WebserverConfig::host_check_flag) + .def_property_readonly("use_retry_flag", &WebserverConfig::use_retry_flag); + + py::class_>(m, "Webserver") + .def_static("make", + [](WebserverConfig const & ws_config) + { + return Webserver::make(ws_config, + PrintJsonSingleton::instance()); + }) + .def_property_readonly("state", &Webserver::state) + .def("register_http_endpoint", &Webserver::register_http_endpoint) + .def("register_stream_endpoint", &Webserver::register_stream_endpoint) + .def("start_webserver", &Webserver::start_webserver) + .def("stop_webserver", &Webserver::stop_webserver) + .def("join_webserver", &Webserver::join_webserver) + .def("__repr__", &Webserver::display_string); + + m.def("make_webserver", + &Webserver::make); + } /*pywebsock*/ + } /*web*/ +} /*namespace xo*/ + +/* end pywebsock.cpp */ diff --git a/src/pywebsock/pywebsock.hpp.in b/src/pywebsock/pywebsock.hpp.in new file mode 100644 index 00000000..4d3ec777 --- /dev/null +++ b/src/pywebsock/pywebsock.hpp.in @@ -0,0 +1,25 @@ +/* @file pywebsock.hpp + * + * automatically generated from src/pywebsock/pywebsock.hpp.in + * see src/pywebsock/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYWEBSOCK_MODULE_NAME(), m) { ... } + */ +#define PYWEBSOCK_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(PYWEBSOCK_MODULE_NAME_STR) + */ +#define PYWEBSOCK_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * PYWEBSOCK_IMPORT_MODULE() + * replaces + * py::module_::import("pywebsock") + */ +#define PYWEBSOCK_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pywebsock.hpp */ From 2868cefc53e10bc1231fb37c769169558d5f5db2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 13:19:16 -0400 Subject: [PATCH 0405/2524] build: fix missing xo deps --- cmake/websockConfig.cmake.in | 6 +++--- src/websock/CMakeLists.txt | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cmake/websockConfig.cmake.in b/cmake/websockConfig.cmake.in index ac43847f..a3dad29f 100644 --- a/cmake/websockConfig.cmake.in +++ b/cmake/websockConfig.cmake.in @@ -4,10 +4,10 @@ include(CMakeFindDependencyMacro) # note: changes to find_dependency() calls here # must coordinate with xo_dependency() calls -# in xo-reactor/src/reactor/CMakeLists.txt +# in xo-websock/src/websock/CMakeLists.txt # -#find_dependency(reflect) -#find_dependency(callback) +find_dependency(reactor) +find_dependency(webutil) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/src/websock/CMakeLists.txt b/src/websock/CMakeLists.txt index 42f37a7d..1071bf7f 100644 --- a/src/websock/CMakeLists.txt +++ b/src/websock/CMakeLists.txt @@ -8,6 +8,9 @@ xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 $ # ---------------------------------------------------------------- # external dependencies +# note: changes to xo_dependency() calls here +# must coordinate with find_dependency() calls in +# xo-websock/cmake/websockConfig.cmake.in xo_dependency(${SELF_LIB} reactor) xo_dependency(${SELF_LIB} webutil) @@ -15,7 +18,3 @@ xo_dependency(${SELF_LIB} webutil) xo_external_target_dependency(${SELF_LIB} Libwebsockets websockets_shared) # see jsoncpp-namespaced-targets.cmake (maybe?) for available targets xo_external_target_dependency(${SELF_LIB} jsoncpp jsoncpp_lib) - -# note: changes to xo_dependency() calls here -# must coordinate with find_dependency() calls in -# xo-websock/cmake/websockConfig.cmake.in From e9b9da29696fe14534add8f25a08bb34688bf132 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 13:47:56 -0400 Subject: [PATCH 0406/2524] build: need pywebutil dep --- src/pywebsock/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pywebsock/CMakeLists.txt b/src/pywebsock/CMakeLists.txt index 0ad6f596..2823da75 100644 --- a/src/pywebsock/CMakeLists.txt +++ b/src/pywebsock/CMakeLists.txt @@ -5,5 +5,5 @@ set(SELF_SRCS pywebsock.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} websock) -#xo_pybind11_header_dependency(${SELF_LIB pyfoo) +xo_pybind11_header_dependency(${SELF_LIB} pywebutil) xo_pybind11_dependency(${SELF_LIB} xo_pyutil) From 500855f426b5aaa808b580b81a5ff8c9e8f60b4f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 16:16:38 -0400 Subject: [PATCH 0407/2524] build: streamline .cmake instructions --- CMakeLists.txt | 35 ++----------------------------- EXAMPLES | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 33 deletions(-) create mode 100644 EXAMPLES diff --git a/CMakeLists.txt b/CMakeLists.txt index af0bae5b..36786fb8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,42 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(xo_pyprocess VERSION 0.1) -enable_language(CXX) -# common XO cmake macros (see github.com:Rconybea/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(xo_macros/xo-project-macros) -# ---------------------------------------------------------------- -# 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 (usually temporary) - -set(PROJECT_CXX_FLAGS "") -add_definitions(${PROJECT_CXX_FLAGS}) - -xo_toplevel_compile_options() - -# ---------------------------------------------------------------- -# sources +xo_cxx_toplevel_options() add_subdirectory(src/pyprocess) -#add_subdirectory(utest) - -# ---------------------------------------------------------------- -# provide find_package() support xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/EXAMPLES b/EXAMPLES new file mode 100644 index 00000000..a8ce9feb --- /dev/null +++ b/EXAMPLES @@ -0,0 +1,57 @@ +process module, using pybind11 to wrap c++ implementation + +To demo + +1. build the kalman project + see path/to/kalman/README + + python-compatible .so will be at: + path/to/kalman/build/process_py/process_py.cpython-39-darwin.so + +2. run python: + $ cd path/to/kalman/build/pyprocess + $ python3 + Python 3.9.12 (main, May 13 2022, 08:13:55) + [Clang 11.1.0 ] on darwin + Type "help", "copyright", "credits" or "license" for more information. + >>> + +3. import pybind11 module and run: + >>> import pyprocess + >>> import datetime as dt + >>> from datetime import datetime as clock + >>> t0=clock.now() + # brownian motion, 50% annual volatility + >>> bm=pyprocess.make_brownian_motion(t0, 0.5) + >>> bm + + >>> bm.exterior_sample(t0, [t0, 2.0]) + 2.0 + >>> bm.exterior_sample(t0, [t0, 1.0]) + 1.0 + >>> bm.exterior_sample(t0 + dt.timedelta(days=30, [t0, 1.0])) + 1.959941114989831 + + >>> ebm=pyprocess.make_exponential_brownian_motion(t0, 0.5) + >>> ebm + + >>> ebm.exterior_sample(t + dt.timedelta(days=180, [t0, 50.0])) + 42.3212369005776 + >>> ebm.exterior_sample(t + dt.timedelta(days=180, [t0, 50.0])) + 56.16317742801309 + + >>> r=pyprocess.make_realization_source(ebm, dt.timedelta(seconds=1)) + +4. attach printer + + >>> import reactor_py + >>> p=reactor_py.make_realization_printer() + >>> r.attach_sink(p) + >>> bm.deliver_one() + >>> bm.deliver_one() + >>> r.deliver_one() + [20220718:224818.909576, 0] + 1 + >>> r.deliver_one() + [20220718:224819.909576, -0.000111338] + 1 From 5668e1b18831ccd25bd61877fb04b5eef7e90363 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 16:25:33 -0400 Subject: [PATCH 0408/2524] build: fix find_package() deps: + reactor+printjson --- cmake/processConfig.cmake.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/processConfig.cmake.in b/cmake/processConfig.cmake.in index fa80b6be..dc6207c5 100644 --- a/cmake/processConfig.cmake.in +++ b/cmake/processConfig.cmake.in @@ -6,8 +6,8 @@ include(CMakeFindDependencyMacro) # must coordinate with xo_dependency() calls # in xo-process/src/process/CMakeLists.txt # -find_dependency(reflect) -#find_dependency(callback) +find_dependency(reactor) +find_dependency(printjson) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From 3645ffaf734b4548fabd226a047db2f507e1a0d3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 16:34:44 -0400 Subject: [PATCH 0409/2524] build: supply xo_pyutil dep with find_package() support --- cmake/xo_pywebutilConfig.cmake.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/xo_pywebutilConfig.cmake.in b/cmake/xo_pywebutilConfig.cmake.in index 9c15f36a..18eaf04d 100644 --- a/cmake/xo_pywebutilConfig.cmake.in +++ b/cmake/xo_pywebutilConfig.cmake.in @@ -1,4 +1,6 @@ @PACKAGE_INIT@ +include(CMakeFindDependencyMacro) +find_dependency(xo_pyutil) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From bb26c99660be1b7e2b76b110bb3834dc713b1b44 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 16:36:51 -0400 Subject: [PATCH 0410/2524] build: tidy using streamlined xo-pywebutil --- cmake/cmake | 4 ---- src/pyprocess/CMakeLists.txt | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 cmake/cmake diff --git a/cmake/cmake b/cmake/cmake deleted file mode 100644 index b49b4828..00000000 --- a/cmake/cmake +++ /dev/null @@ -1,4 +0,0 @@ - /home/roland/proj/xo-pyprocess/cmake: - drwxr-xr-x 2 roland roland 4096 Oct 12 21:49 . - drwxr-xr-x 6 roland roland 4096 Oct 12 21:49 .. - -rw-r--r-- 1 roland roland 125 Oct 12 21:49 xo_pyprocessConfig.cmake.in diff --git a/src/pyprocess/CMakeLists.txt b/src/pyprocess/CMakeLists.txt index 03237164..cf35ab2e 100644 --- a/src/pyprocess/CMakeLists.txt +++ b/src/pyprocess/CMakeLists.txt @@ -6,5 +6,5 @@ set(SELF_SRCS pyprocess.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} process) -xo_pybind11_header_dependency(${SELF_LIB} pyreactor) -xo_pybind11_header_dependency(${SELF_LIB} pywebutil) +xo_pybind11_header_dependency(${SELF_LIB} xo_pyreactor) +xo_pybind11_header_dependency(${SELF_LIB} xo_pywebutil) From 52df00721c19179e9f53a5391cf1c3a4e0cfc350 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 16:39:27 -0400 Subject: [PATCH 0411/2524] bugfix: SELF_LIBRARY_NAME->SELF_LIB in .hpp.in --- src/pyreactor/pyreactor.cpp | 4 ++-- src/pyreactor/pyreactor.hpp.in | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pyreactor/pyreactor.cpp b/src/pyreactor/pyreactor.cpp index 5f8cf77b..a5120f96 100644 --- a/src/pyreactor/pyreactor.cpp +++ b/src/pyreactor/pyreactor.cpp @@ -1,4 +1,4 @@ -/* @file ReactorPy.cpp */ +/* @file pyreactor.cpp */ #include "pyreactor.hpp" #include "xo/pyprintjson/pyprintjson.hpp" @@ -138,4 +138,4 @@ namespace xo { } /*namespace reactor*/ } /*namespace xo*/ -/* end ReactorPy.cpp */ +/* end pyreactor.cpp */ diff --git a/src/pyreactor/pyreactor.hpp.in b/src/pyreactor/pyreactor.hpp.in index edbb2e78..140ded1b 100644 --- a/src/pyreactor/pyreactor.hpp.in +++ b/src/pyreactor/pyreactor.hpp.in @@ -8,18 +8,18 @@ * example: * PYBIND11_MODULE(PYREACTOR_MODULE_NAME(), m) { ... } */ -#define PYREACTOR_MODULE_NAME() @SELF_LIBRARY_NAME@ +#define PYREACTOR_MODULE_NAME() @SELF_LIB@ /* example: * py::module_::import(PYREACTOR_MODULE_NAME_STR) */ -#define PYREACTOR_MODULE_NAME_STR "@SELF_LIBRARY_NAME@" +#define PYREACTOR_MODULE_NAME_STR "@SELF_LIB@" /* example: * PYREACTOR_IMPORT_MODULE() * replaces * py::module_::import("pyreactor") */ -#define PYREACTOR_IMPORT_MODULE() py::module_::import("@SELF_LIBRARY_NAME@") +#define PYREACTOR_IMPORT_MODULE() py::module_::import("@SELF_LIB@") /* end pyreactor.hpp */ From 5327277ee4a83bdcd6f61a0cc9e7f8791c88b62a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 17:07:54 -0400 Subject: [PATCH 0412/2524] build: pyreactor -> xo_pyreactor --- src/pykalmanfilter/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pykalmanfilter/CMakeLists.txt b/src/pykalmanfilter/CMakeLists.txt index f551c368..af687d48 100644 --- a/src/pykalmanfilter/CMakeLists.txt +++ b/src/pykalmanfilter/CMakeLists.txt @@ -1,12 +1,12 @@ # xo_pykalmanfilter/src/pykalmanfilter/CMakeLists.txt -set(SELF_LIB pykalmanfilter) +set(SELF_LIB xo_pykalmanfilter) set(SELF_SRCS pykalmanfilter.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} xo_kalmanfilter) -xo_pybind11_header_dependency(${SELF_LIB} pyreactor) -xo_pybind11_dependency(${SELF_LIB} xo_pyutil) +xo_pybind11_header_dependency(${SELF_LIB} xo_pyreactor) +#xo_pybind11_dependency(${SELF_LIB} xo_pyutil) # cmake config generated by xo-kalman filter # (xo-kalmanfilter/cmake/xo_kalmanfilterConfig.cmake) From 5529e36b8136e98af4cba7c58767c88f1dc3c7e2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 17:08:20 -0400 Subject: [PATCH 0413/2524] build: pyreactor -> xo_pyreactor --- src/pyreactor/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyreactor/CMakeLists.txt b/src/pyreactor/CMakeLists.txt index b7d5695f..501a2572 100644 --- a/src/pyreactor/CMakeLists.txt +++ b/src/pyreactor/CMakeLists.txt @@ -1,6 +1,6 @@ # xo_pyreactor/src/pyreactor/CMakeLists.txt -set(SELF_LIB pyreactor) +set(SELF_LIB xo_pyreactor) set(SELF_SRCS pyreactor.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) From 8c1c0c181102ab69867201a6da7c10e5850e6db0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 17:08:47 -0400 Subject: [PATCH 0414/2524] build: pywebutil -> xo_pywebutil --- src/pywebsock/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pywebsock/CMakeLists.txt b/src/pywebsock/CMakeLists.txt index 2823da75..57c5d75a 100644 --- a/src/pywebsock/CMakeLists.txt +++ b/src/pywebsock/CMakeLists.txt @@ -5,5 +5,5 @@ set(SELF_SRCS pywebsock.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} websock) -xo_pybind11_header_dependency(${SELF_LIB} pywebutil) +xo_pybind11_header_dependency(${SELF_LIB} xo_pywebutil) xo_pybind11_dependency(${SELF_LIB} xo_pyutil) From abd1fb2040d69f8d958c4e1789c827dfa801c706 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 17:09:10 -0400 Subject: [PATCH 0415/2524] build: pywebutil -> xo_pywebutil --- src/pywebutil/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pywebutil/CMakeLists.txt b/src/pywebutil/CMakeLists.txt index 95f8df26..cc68d6e1 100644 --- a/src/pywebutil/CMakeLists.txt +++ b/src/pywebutil/CMakeLists.txt @@ -1,6 +1,6 @@ # xo-pywebutil/src/pywebutil/CMakeLists.txt -set(SELF_LIB pywebutil) +set(SELF_LIB xo_pywebutil) set(SELF_SRCS pywebutil.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) From 037b34d9e903e11ba53b23dcf384527ba7a6b214 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 17:25:44 -0400 Subject: [PATCH 0416/2524] build: pyprintjson -> xo-pyprintjson --- src/pyreactor/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyreactor/CMakeLists.txt b/src/pyreactor/CMakeLists.txt index 501a2572..686e0b69 100644 --- a/src/pyreactor/CMakeLists.txt +++ b/src/pyreactor/CMakeLists.txt @@ -6,4 +6,4 @@ set(SELF_SRCS pyreactor.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} reactor) -xo_pybind11_header_dependency(${SELF_LIB} pyprintjson) +xo_pybind11_header_dependency(${SELF_LIB} xo_pyprintjson) From 389a6375756da93aa8c7cc3f5aa64ab59fc8266f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 17:39:14 -0400 Subject: [PATCH 0417/2524] github: + xo-ordinaltree dep --- .github/workflows/main.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 30ac4a91..7efc00fe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -196,6 +196,25 @@ jobs: # ---------------------------------------------------------------- + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + - name: Configure self (reactor) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 484b2e9f0d3118dbfae826a2beaa9417b13df0c3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 20:36:44 -0400 Subject: [PATCH 0418/2524] cosmetic: drop unneeded comments --- src/pyreflect/CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/pyreflect/CMakeLists.txt b/src/pyreflect/CMakeLists.txt index 94a71814..e53926d0 100644 --- a/src/pyreflect/CMakeLists.txt +++ b/src/pyreflect/CMakeLists.txt @@ -3,10 +3,5 @@ set(SELF_LIB pyreflect) set(SELF_SRCS pyreflect.cpp) -# ---------------------------------------------------------------- -# pybind11 dep - xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) - xo_pybind11_dependency(${SELF_LIB} reflect) - From ba0b989b6dab3cf8bb099d0f73ac2e63cb8bbe10 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:08:35 -0400 Subject: [PATCH 0419/2524] build: bugfix for .cmake eigen dep --- cmake/xo_kalmanfilterConfig.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/xo_kalmanfilterConfig.cmake.in b/cmake/xo_kalmanfilterConfig.cmake.in index 366e45ba..950e70c0 100644 --- a/cmake/xo_kalmanfilterConfig.cmake.in +++ b/cmake/xo_kalmanfilterConfig.cmake.in @@ -7,7 +7,7 @@ include(CMakeFindDependencyMacro) # in xo-reactor/src/reactor/CMakeLists.txt # find_dependency(reactor) -find_dependnecy(eigen3) +find_dependency(Eigen3) #find_dependency(reflect) #find_dependency(webutil) #find_dependency(printjson) From f95cd5edc5ce5bac3f8e55530fdc26c38f626739 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:09:49 -0400 Subject: [PATCH 0420/2524] + .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build From e72c0d27af5ab35a346dc471b001d13c05d7c320 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:10:11 -0400 Subject: [PATCH 0421/2524] build: bugfix: pywebutil -> xo_pywebutil --- src/pywebutil/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pywebutil/CMakeLists.txt b/src/pywebutil/CMakeLists.txt index 95f8df26..cc68d6e1 100644 --- a/src/pywebutil/CMakeLists.txt +++ b/src/pywebutil/CMakeLists.txt @@ -1,6 +1,6 @@ # xo-pywebutil/src/pywebutil/CMakeLists.txt -set(SELF_LIB pywebutil) +set(SELF_LIB xo_pywebutil) set(SELF_SRCS pywebutil.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) From 8472e4196d7627331a5a7893b226b87b3f3697e7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:11:18 -0400 Subject: [PATCH 0422/2524] + .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d6536bad --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +compile_commands.json From 9dc0c6e25695d4d67e87e7ee4a504e5a86661f98 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:13:04 -0400 Subject: [PATCH 0423/2524] pyprintjson -> xo_pyprintjson --- src/pyprintjson/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyprintjson/CMakeLists.txt b/src/pyprintjson/CMakeLists.txt index 5e77c995..2370e6a2 100644 --- a/src/pyprintjson/CMakeLists.txt +++ b/src/pyprintjson/CMakeLists.txt @@ -1,6 +1,6 @@ # xo_pyprintjson/src/pyprintjson/CMakeLists.txt -set(SELF_LIB pyprintjson) +set(SELF_LIB xo_pyprintjson) set(SELF_SRCS pyprintjson.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) From 9d412f10e19099c39e13bb51354e7684acbb099f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:15:00 -0400 Subject: [PATCH 0424/2524] + .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build From aa539e1075a9d612ad7028f2b39f462e8ffcdfd0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:15:43 -0400 Subject: [PATCH 0425/2524] + .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d6536bad --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +compile_commands.json From e4169def1ca0b6ab541b2b126e7758bfc3392674 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:18:06 -0400 Subject: [PATCH 0426/2524] tweak message on INTERFACE_LINK_LIBRARIES --- cmake/xo_macros/xo_cxx.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 06a28990..eb389ad7 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -884,7 +884,7 @@ macro(xo_pybind11_dependency target dep) # ok to keep dep libraries on link line in submodule build #message("xo_pybind11_dependency: ${target}: don't clobber ${dep}.INTERFACE_LINK_LIBRARIES") else() - message("xo_pybind11_dependency: ${target}: clobbering ${dep}.INTERFACE_LINK_LIBRARIES") + message("xo_pybind11_dependency: ${target}: remove ${dep}.INTERFACE_LINK_LIBRARIES to avoid problems with transitive deps") set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") # also have to clobber libraries for From 09cb38f26565d769d89bee5eefadd5fe3bfad3db Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:19:09 -0400 Subject: [PATCH 0427/2524] + .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d6536bad --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +compile_commands.json From 45a81a2a7a77b87fe0703834390b5eb34e8a30b0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:19:57 -0400 Subject: [PATCH 0428/2524] + .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build From 31105757e6e41b5f5438dd1a2fab3656acb01fab Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 24 Oct 2023 22:20:14 -0400 Subject: [PATCH 0429/2524] tidy -- remove dead model file --- CMakeLists.txt.old | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 CMakeLists.txt.old diff --git a/CMakeLists.txt.old b/CMakeLists.txt.old deleted file mode 100644 index e85d245f..00000000 --- a/CMakeLists.txt.old +++ /dev/null @@ -1,37 +0,0 @@ -# filter/CMakeLists.txt - -set(SELF_LIBRARY_NAME filter) - -set(SELF_SOURCE_FILES KalmanFilterSvc.cpp KalmanFilter.cpp KalmanFilterState.cpp KalmanFilterTransition.cpp KalmanFilterObservable.cpp KalmanFilterInput.cpp KalmanFilterStep.cpp KalmanFilterSpec.cpp KalmanFilterEngine.cpp KalmanFilterInputToConsole.cpp KalmanFilterStateToConsole.cpp EigenUtil.cpp init_filter.cpp) - -# build shared liburary 'filter' -add_library(${SELF_LIBRARY_NAME} SHARED ${SELF_SOURCE_FILES}) - -set_target_properties(${SELF_LIBRARY_NAME} - PROPERTIES - VERSION ${PROJECT_VERSION} - SOVERSION 1 - PUBLIC_HEADER init_filter.hpp) - -# ---------------------------------------------------------------- -# all the warnings! -# -xo_compile_options(${SELF_LIBRARY_NAME}) -xo_include_options(${SELF_LIBRARY_NAME}) - -# ---------------------------------------------------------------- -# internal dependencies: reactor, ... - -#target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC process) -target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC reactor) -target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC printjson) -target_link_libraries(${SELF_LIBRARY_NAME} PUBLIC refcnt) - -# ---------------------------------------------------------------- -# 3rd party dependency: eigen: - -xo_eigen_dependency(${SELF_LIBRARY_NAME}) - -xo_install_library(${SELF_LIBRARY_NAME}) - -# end CMakeLists.txt From b0b3e7c0c8b297fad95daefa4ad197f1828e9592 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 13 Mar 2024 15:30:00 -0400 Subject: [PATCH 0430/2524] cosmetic: layout, copypasta comments --- utest/StructTdx.test.cpp | 76 +++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/utest/StructTdx.test.cpp b/utest/StructTdx.test.cpp index cb845e18..6b1fb2cd 100644 --- a/utest/StructTdx.test.cpp +++ b/utest/StructTdx.test.cpp @@ -7,53 +7,51 @@ #include namespace xo { - using xo::reflect::Reflect; - using xo::reflect::TaggedPtr; - using xo::reflect::TypeDescr; - using xo::reflect::Metatype; + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::TypeDescr; + using xo::reflect::Metatype; - namespace ut { - TEST_CASE("std-pair-reflect", "[reflect]") { - std::pair p; + namespace ut { + TEST_CASE("std-pair-reflect", "[reflect]") { + std::pair p; - TaggedPtr tp = Reflect::make_tp(&p); - //TypeDescr td = Reflect::require>(); + TaggedPtr tp = Reflect::make_tp(&p); - REQUIRE(Reflect::is_reflected>() == true); + REQUIRE(Reflect::is_reflected>() == true); - REQUIRE(tp.td()->complete_flag()); - REQUIRE(tp.address() == &p); - REQUIRE(tp.is_struct()); - REQUIRE(tp.is_vector() == false); - REQUIRE(tp.td()->metatype() == Metatype::mt_struct); - REQUIRE(tp.recover_native>() == &p); - REQUIRE(tp.n_child() == 2); /* struct with 2 members */ - REQUIRE(tp.struct_member_name(0) == "first"); - REQUIRE(tp.struct_member_name(1) == "second"); + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &p); + REQUIRE(tp.is_struct()); + REQUIRE(tp.is_vector() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_struct); + REQUIRE(tp.recover_native>() == &p); + REQUIRE(tp.n_child() == 2); /* struct with 2 members */ + REQUIRE(tp.struct_member_name(0) == "first"); + REQUIRE(tp.struct_member_name(1) == "second"); - TaggedPtr tp0 = tp.get_child(0); + TaggedPtr tp0 = tp.get_child(0); - REQUIRE(tp0.td()->complete_flag()); - REQUIRE(tp0.address() == &(p.first)); - REQUIRE(!tp0.is_vector()); - REQUIRE(!tp0.is_struct()); - REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); - REQUIRE(tp0.recover_native() == &(p.first)); - REQUIRE(tp0.n_child() == 0); + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(p.first)); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(p.first)); + REQUIRE(tp0.n_child() == 0); - TaggedPtr tp1 = tp.get_child(1); + TaggedPtr tp1 = tp.get_child(1); - REQUIRE(tp1.td()->complete_flag()); - REQUIRE(tp1.address() == &(p.second)); - REQUIRE(!tp1.is_vector()); - REQUIRE(!tp1.is_struct()); - REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); - REQUIRE(tp1.recover_native() == &(p.second)); - REQUIRE(tp1.n_child() == 0); + REQUIRE(tp1.td()->complete_flag()); + REQUIRE(tp1.address() == &(p.second)); + REQUIRE(!tp1.is_vector()); + REQUIRE(!tp1.is_struct()); + REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp1.recover_native() == &(p.second)); + REQUIRE(tp1.n_child() == 0); - } /*TEST_CASE(std-pair-reflect)*/ - - } /*namespace ut*/ + } /*TEST_CASE(std-pair-reflect)*/ + } /*namespace ut*/ } /*namespace xo*/ -/* end VectorTdx.test.cpp */ +/* end StructTdx.test.cpp */ From 93910f901300825e6348bed1806e6b534b7d15b2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 13 Mar 2024 16:34:07 -0400 Subject: [PATCH 0431/2524] pybind11 python-fetching deprecated, drop it --- cmake/xo_macros/xo_cxx.cmake | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 1f392ad7..f0b62c86 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -803,13 +803,7 @@ macro(xo_pybind11_library target projectTargets source_files) DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${_nxo_target}) endif() - # find_package(Python..) finds python in - # /Library/Frameworks/Python.framework/... - # but we want to use python from nix - # - #find_package(Python COMPONENTS Interpreter Development REQUIRED) - # - + find_package(Python COMPONENTS Interpreter Development REQUIRED) find_package(pybind11) # this only works if one source file, right? From ccdf8d467f088e99dd659f1f4ba60d34d1121616 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 13 Mar 2024 16:36:15 -0400 Subject: [PATCH 0432/2524] websock: export libwebsockets dep --- cmake/websockConfig.cmake.in | 1 + src/websock/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/cmake/websockConfig.cmake.in b/cmake/websockConfig.cmake.in index a3dad29f..68730bdb 100644 --- a/cmake/websockConfig.cmake.in +++ b/cmake/websockConfig.cmake.in @@ -8,6 +8,7 @@ include(CMakeFindDependencyMacro) # find_dependency(reactor) find_dependency(webutil) +find_dependency(Libwebsockets websockets_shared) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/src/websock/CMakeLists.txt b/src/websock/CMakeLists.txt index 1071bf7f..0debcb3c 100644 --- a/src/websock/CMakeLists.txt +++ b/src/websock/CMakeLists.txt @@ -15,6 +15,7 @@ xo_dependency(${SELF_LIB} reactor) xo_dependency(${SELF_LIB} webutil) # see LibwebsocketsTargets-release.cmake for available targets +# this dependency doesn't show up via cmake-export xo_external_target_dependency(${SELF_LIB} Libwebsockets websockets_shared) # see jsoncpp-namespaced-targets.cmake (maybe?) for available targets xo_external_target_dependency(${SELF_LIB} jsoncpp jsoncpp_lib) From d34315d33f485fd5affb810979fcf5880c155b3b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 13 Mar 2024 16:50:58 -0400 Subject: [PATCH 0433/2524] bugfix: find_dependency() extra arg --- cmake/websockConfig.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/websockConfig.cmake.in b/cmake/websockConfig.cmake.in index 68730bdb..3e03d576 100644 --- a/cmake/websockConfig.cmake.in +++ b/cmake/websockConfig.cmake.in @@ -8,7 +8,7 @@ include(CMakeFindDependencyMacro) # find_dependency(reactor) find_dependency(webutil) -find_dependency(Libwebsockets websockets_shared) +find_dependency(Libwebsockets) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") From ca721658e9d4f2b892c86b47cf48cafd8bc3bd92 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:20:18 -0400 Subject: [PATCH 0434/2524] stremaline xo-cmake info messages --- .gitignore | 2 +- cmake/xo_macros/xo_cxx.cmake | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 378eac25..24e5b0a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -build +.build diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index f0b62c86..e63762f8 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -557,7 +557,7 @@ macro(xo_dependency_helper target visibility dep) xo_dependency_helper1(${target} ${visibility} repo/${_nxo_dep}/include) endif() else() - message("xo_dependency_helper: find_package() on ${dep} for ${target}") + message("-- [${target}] find_package(${dep}) (xo_dependency_helper)") find_package(${dep} CONFIG REQUIRED) endif() @@ -713,6 +713,7 @@ endmacro() # target_link_libraries(foo PUBLIC Catch2::Catch2) # macro(xo_external_target_dependency target pkg pkgtarget) + message("-- [${target}] find_package(${pkg}) (xo_external_target_dependency)") find_package(${pkg} CONFIG REQUIRED) target_link_libraries(${target} PUBLIC ${pkgtarget}) #target_link_libraries(${target} ${pkgtarget}) @@ -803,7 +804,9 @@ macro(xo_pybind11_library target projectTargets source_files) DESTINATION ${CMAKE_INSTALL_PREFIX}/include/xo/${_nxo_target}) endif() + message("-- [${target}] find_package(Python) (xo_pybind11_library)") find_package(Python COMPONENTS Interpreter Development REQUIRED) + message("-- [${target}] find_package(pybind11) (xo_pybind11_library)") find_package(pybind11) # this only works if one source file, right? @@ -878,7 +881,7 @@ macro(xo_pybind11_dependency target dep) # ok to keep dep libraries on link line in submodule build #message("xo_pybind11_dependency: ${target}: don't clobber ${dep}.INTERFACE_LINK_LIBRARIES") else() - message("xo_pybind11_dependency: ${target}: remove ${dep}.INTERFACE_LINK_LIBRARIES to avoid problems with transitive deps") + message("-- [${target}] remove ${dep}.INTERFACE_LINK_LIBRARIES to avoid problems with transitive deps (xo_pybind11_dependency)") set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") # also have to clobber libraries for From c40f5f3ccf2a5b05be83f56272675876b1b0079a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:24:55 -0400 Subject: [PATCH 0435/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 3 +- CMakeLists.txt | 3 +- cmake/xo-bootstrap-macros.cmake | 12 + utest/intrusive_ptr.test.cpp | 420 ++++++++++++++++---------------- 4 files changed, 224 insertions(+), 214 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 86d062ab..9648517d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,4 @@ compile_commands.json # LSP keeps state here .cache # typical build directories -build -ccov +.build* diff --git a/CMakeLists.txt b/CMakeLists.txt index bcb080d4..e9176eee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(refcnt VERSION 0.1) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/utest/intrusive_ptr.test.cpp b/utest/intrusive_ptr.test.cpp index bc45d38c..890832b7 100644 --- a/utest/intrusive_ptr.test.cpp +++ b/utest/intrusive_ptr.test.cpp @@ -7,295 +7,295 @@ #include namespace xo { - using xo::ref::Refcount; - using xo::ref::Borrow; - using xo::ref::rp; - using xo::ref::brw; - using xo::ref::intrusive_ptr_refcount; - using xo::ref::intrusive_ptr_add_ref; - using xo::ref::intrusive_ptr_release; + using xo::ref::Refcount; + using xo::ref::Borrow; + using xo::ref::rp; + using xo::ref::brw; + using xo::ref::intrusive_ptr_refcount; + using xo::ref::intrusive_ptr_add_ref; + using xo::ref::intrusive_ptr_release; - namespace ut { - namespace { - static uint32_t ctor_count = 0; - static uint32_t dtor_count = 0; + namespace ut { + namespace { + static uint32_t ctor_count = 0; + static uint32_t dtor_count = 0; - /* empty object, except for refcount */ - class JustRefcount : public ref::Refcount { - public: - JustRefcount() { ++ctor_count; } - ~JustRefcount() { ++dtor_count; } - }; /*JustRefcount*/ + /* empty object, except for refcount */ + class JustRefcount : public ref::Refcount { + public: + JustRefcount() { ++ctor_count; } + ~JustRefcount() { ++dtor_count; } + }; /*JustRefcount*/ - inline std::ostream & operator<<(std::ostream & os, JustRefcount & x) { - os << "JustRefcount"; - return os; - } /*operator<<*/ - } /*namespace*/ + inline std::ostream & operator<<(std::ostream & os, JustRefcount & x) { + os << "JustRefcount"; + return os; + } /*operator<<*/ + } /*namespace*/ - TEST_CASE("refcount", "[refcnt][trivial]") { - REQUIRE(std::is_default_constructible() == true); - REQUIRE(std::has_virtual_destructor() == true); + TEST_CASE("refcount", "[refcnt][trivial]") { + REQUIRE(std::is_default_constructible() == true); + REQUIRE(std::has_virtual_destructor() == true); - /* refcount object self-initializes to 0 */ - Refcount x; - REQUIRE(x.reference_counter() == 0); - } /*TEST_CASE(refcount)*/ + /* refcount object self-initializes to 0 */ + Refcount x; + REQUIRE(x.reference_counter() == 0); + } /*TEST_CASE(refcount)*/ - TEST_CASE("null-intrusive-ptr", "[refcnt][trivial]") { - //constexpr std::string_view c_self = "TEST_CASE:null-intrusive-ptr"; + TEST_CASE("null-intrusive-ptr", "[refcnt][trivial]") { + //constexpr std::string_view c_self = "TEST_CASE:null-intrusive-ptr"; - REQUIRE(std::has_virtual_destructor() == true); + REQUIRE(std::has_virtual_destructor() == true); - rp p1; - rp p2; + rp p1; + rp p2; - REQUIRE(sizeof(p1) == sizeof(JustRefcount*)); + REQUIRE(sizeof(p1) == sizeof(JustRefcount*)); - REQUIRE(p1.get() == nullptr); - REQUIRE(p1.operator->() == nullptr); + REQUIRE(p1.get() == nullptr); + REQUIRE(p1.operator->() == nullptr); - REQUIRE(p2.get() == nullptr); - REQUIRE(p2.operator->() == nullptr); + REQUIRE(p2.get() == nullptr); + REQUIRE(p2.operator->() == nullptr); - /* can assign a nullptr */ - rp p3; + /* can assign a nullptr */ + rp p3; - REQUIRE(p3.get() == nullptr); - p3 = p1; - REQUIRE(p3.get() == nullptr); + REQUIRE(p3.get() == nullptr); + p3 = p1; + REQUIRE(p3.get() == nullptr); - /* can use aux functions on null pointers */ - REQUIRE(intrusive_ptr_refcount(p1.get()) == 0); + /* can use aux functions on null pointers */ + REQUIRE(intrusive_ptr_refcount(p1.get()) == 0); - intrusive_ptr_add_ref(nullptr); - intrusive_ptr_release(nullptr); + intrusive_ptr_add_ref(nullptr); + intrusive_ptr_release(nullptr); - /* can borrow a null intrusive_ptr */ - brw p1_brw = p1.borrow(); - brw p2_brw = p2.borrow(); + /* can borrow a null intrusive_ptr */ + brw p1_brw = p1.borrow(); + brw p2_brw = p2.borrow(); - REQUIRE(p1_brw.get() == nullptr); - REQUIRE(p1_brw.operator->() == nullptr); - /* null borrow is false-y */ - REQUIRE(p1_brw == false); + REQUIRE(p1_brw.get() == nullptr); + REQUIRE(p1_brw.operator->() == nullptr); + /* null borrow is false-y */ + REQUIRE(p1_brw == false); - /* can promote a borrowed pointer */ - rp pp = p1_brw.promote(); + /* can promote a borrowed pointer */ + rp pp = p1_brw.promote(); - REQUIRE(p1.get() == pp.get()); + REQUIRE(p1.get() == pp.get()); - /* comparisons */ - REQUIRE(Borrow::compare(p1_brw, p2_brw) == 0); - REQUIRE(p1_brw == p2_brw); - REQUIRE((p1_brw != p2_brw) == false); - REQUIRE(p1 == p1_brw); - REQUIRE((p1 != p1_brw) == false); - REQUIRE(p1_brw == p1); - REQUIRE((p1_brw != p1) == false); - } /*TEST_CASE(null-intrusive_ptr)*/ + /* comparisons */ + REQUIRE(Borrow::compare(p1_brw, p2_brw) == 0); + REQUIRE(p1_brw == p2_brw); + REQUIRE((p1_brw != p2_brw) == false); + REQUIRE(p1 == p1_brw); + REQUIRE((p1 != p1_brw) == false); + REQUIRE(p1_brw == p1); + REQUIRE((p1_brw != p1) == false); + } /*TEST_CASE(null-intrusive_ptr)*/ - TEST_CASE("intrusive-ptr-identity", "[refcnt][identity]") - { - uint32_t cc = ctor_count; - uint32_t dc = dtor_count; + TEST_CASE("intrusive-ptr-identity", "[refcnt][identity]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; - rp p1(new JustRefcount()); + rp p1(new JustRefcount()); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); - REQUIRE(p1.get() != nullptr); - REQUIRE(p1.get() == p1.operator->()); - REQUIRE(intrusive_ptr_refcount(p1.get()) == 1); - REQUIRE(p1->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1.get() == p1.operator->()); + REQUIRE(intrusive_ptr_refcount(p1.get()) == 1); + REQUIRE(p1->reference_counter() == 1); - intrusive_ptr_add_ref(p1.get()); + intrusive_ptr_add_ref(p1.get()); - REQUIRE(intrusive_ptr_refcount(p1.get()) == 2); + REQUIRE(intrusive_ptr_refcount(p1.get()) == 2); - intrusive_ptr_release(p1.get()); + intrusive_ptr_release(p1.get()); - REQUIRE(intrusive_ptr_refcount(p1.get()) == 1); + REQUIRE(intrusive_ptr_refcount(p1.get()) == 1); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - rp p2(new JustRefcount()); + rp p2(new JustRefcount()); - REQUIRE(ctor_count == cc + 2); - REQUIRE(dtor_count == dc); + REQUIRE(ctor_count == cc + 2); + REQUIRE(dtor_count == dc); - REQUIRE(p2.get() != nullptr); - REQUIRE(p2.get() != p1.get()); - REQUIRE(p2.get() == p2.operator->()); - REQUIRE(p2->reference_counter() == 1); + REQUIRE(p2.get() != nullptr); + REQUIRE(p2.get() != p1.get()); + REQUIRE(p2.get() == p2.operator->()); + REQUIRE(p2->reference_counter() == 1); - /* can borrow a non-null intrusive-ptr */ - brw p1_brw = p1.borrow(); + /* can borrow a non-null intrusive-ptr */ + brw p1_brw = p1.borrow(); - REQUIRE(p1_brw.get() == p1.get()); + REQUIRE(p1_brw.get() == p1.get()); - /* borrowing does not change refcount, borrow not tracked */ - REQUIRE(ctor_count == cc + 2); - REQUIRE(dtor_count == dc); - REQUIRE(p1.get()->reference_counter() == 1); + /* borrowing does not change refcount, borrow not tracked */ + REQUIRE(ctor_count == cc + 2); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get()->reference_counter() == 1); - /* copying borrowed pointer does not touch refcount */ - brw p1_brw2 = p1_brw; + /* copying borrowed pointer does not touch refcount */ + brw p1_brw2 = p1_brw; - REQUIRE(ctor_count == cc + 2); - REQUIRE(dtor_count == dc); - REQUIRE(p1_brw2.get() == p1.get()); + REQUIRE(ctor_count == cc + 2); + REQUIRE(dtor_count == dc); + REQUIRE(p1_brw2.get() == p1.get()); - REQUIRE(p1.get()->reference_counter() == 1); - } /*TEST_CASE(identity-intrusive-ptr)*/ + REQUIRE(p1.get()->reference_counter() == 1); + } /*TEST_CASE(identity-intrusive-ptr)*/ - TEST_CASE("intrusive-ptr-release", "[refcnt][release]") - { - uint32_t cc = ctor_count; - uint32_t dc = dtor_count; + TEST_CASE("intrusive-ptr-release", "[refcnt][release]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; - rp p1(new JustRefcount()); + rp p1(new JustRefcount()); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); - REQUIRE(p1.get() != nullptr); - REQUIRE(p1->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); - /* reference count going to 0 -> delete object */ - p1 = nullptr; + /* reference count going to 0 -> delete object */ + p1 = nullptr; - REQUIRE(p1.get() == nullptr); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc + 1); - } /*TEST_CASE(intrusive-ptr-release)*/ + REQUIRE(p1.get() == nullptr); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-release)*/ - TEST_CASE("intrusive-ptr-copy", "[refcnt][copy]") - { - uint32_t cc = ctor_count; - uint32_t dc = dtor_count; + TEST_CASE("intrusive-ptr-copy", "[refcnt][copy]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; - rp p1(new JustRefcount()); - JustRefcount * p1_native = p1.get(); + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); - REQUIRE(p1.get() != nullptr); - REQUIRE(p1->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); - /* copy ctor ran to make copy of p1, did not allocate */ - rp p2(p1); + /* copy ctor ran to make copy of p1, did not allocate */ + rp p2(p1); - REQUIRE(p1->reference_counter() == 2); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(p1->reference_counter() == 2); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - } /*TEST_CASE(intrusive-ptr-copy)*/ + } /*TEST_CASE(intrusive-ptr-copy)*/ - TEST_CASE("intrusive-ptr-move", "[refcnt][move]") - { - uint32_t cc = ctor_count; - uint32_t dc = dtor_count; + TEST_CASE("intrusive-ptr-move", "[refcnt][move]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; - rp p1(new JustRefcount()); - JustRefcount * p1_native = p1.get(); + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); - REQUIRE(p1.get() != nullptr); - REQUIRE(p1->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); - rp p2{std::move(p1)}; + rp p2{std::move(p1)}; - REQUIRE(p2->reference_counter() == 1); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(p2->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - p2 = nullptr; + p2 = nullptr; - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc + 1); - } /*TEST_CASE(intrusive-ptr-move)*/ + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-move)*/ - TEST_CASE("instrusive-ptr-assign", "[refcnt][assign]") - { - uint32_t cc = ctor_count; - uint32_t dc = dtor_count; + TEST_CASE("instrusive-ptr-assign", "[refcnt][assign]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; - rp p1(new JustRefcount()); - JustRefcount * p1_native = p1.get(); + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); - REQUIRE(p1.get() != nullptr); - REQUIRE(p1->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); - rp p2; + rp p2; - REQUIRE(p2.get() == nullptr); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(p2.get() == nullptr); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - p2 = p1; + p2 = p1; - REQUIRE(p2.get() == p1.get()); - REQUIRE(p2->reference_counter() == 2); + REQUIRE(p2.get() == p1.get()); + REQUIRE(p2->reference_counter() == 2); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - p1 = nullptr; + p1 = nullptr; - REQUIRE(p2->reference_counter() == 1); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(p2->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - p2 = nullptr; + p2 = nullptr; - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc + 1); - } /*TEST_CASE(intrusive-ptr-assign)*/ + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-assign)*/ - TEST_CASE("intrusive-ptr-move-assign", "[refcnt][move-assign]") - { - uint32_t cc = ctor_count; - uint32_t dc = dtor_count; + TEST_CASE("intrusive-ptr-move-assign", "[refcnt][move-assign]") + { + uint32_t cc = ctor_count; + uint32_t dc = dtor_count; - rp p1(new JustRefcount()); - JustRefcount * p1_native = p1.get(); + rp p1(new JustRefcount()); + JustRefcount * p1_native = p1.get(); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); - REQUIRE(p1.get() != nullptr); - REQUIRE(p1->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); + REQUIRE(p1.get() != nullptr); + REQUIRE(p1->reference_counter() == 1); - rp p2; + rp p2; - REQUIRE(p2.get() == nullptr); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(p2.get() == nullptr); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - p2 = std::move(p1); + p2 = std::move(p1); - REQUIRE(p1.get() == nullptr); - REQUIRE(p2.get() == p1_native); - REQUIRE(p2->reference_counter() == 1); + REQUIRE(p1.get() == nullptr); + REQUIRE(p2.get() == p1_native); + REQUIRE(p2->reference_counter() == 1); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - p1 = nullptr; /*no-op*/ + p1 = nullptr; /*no-op*/ - REQUIRE(p2->reference_counter() == 1); - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc); + REQUIRE(p2->reference_counter() == 1); + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc); - p2 = nullptr; + p2 = nullptr; - REQUIRE(ctor_count == cc + 1); - REQUIRE(dtor_count == dc + 1); - } /*TEST_CASE(intrusive-ptr-move-assign)*/ - } /*namespace ut*/ + REQUIRE(ctor_count == cc + 1); + REQUIRE(dtor_count == dc + 1); + } /*TEST_CASE(intrusive-ptr-move-assign)*/ + } /*namespace ut*/ } /*namespace xo*/ /* end intrusive_ptr.test.cpp */ From 8f11a8e413e22e2c651d8fda388875e2a5915ef3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:25:48 -0400 Subject: [PATCH 0436/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 8 ++++++-- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index d6bd1a25..9648517d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ -# typical build location -build +# symlink to ${mybuilddirectory}/compile_commands.json for LSP +compile_commands.json +# LSP keeps state here +.cache +# typical build directories +.build* diff --git a/CMakeLists.txt b/CMakeLists.txt index 766058b1..9d6fef68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,8 +3,7 @@ cmake_minimum_required(VERSION 3.10) project(subsys VERSION 0.1) enable_language(CXX) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) enable_testing() # activate code coverage for all executables + libraries (when -DCODE_COVERAGE=ON) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From fe986a320a2b1993a8399602cb3a3b6dc8c86253 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:26:43 -0400 Subject: [PATCH 0437/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index eff45bd2..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # clangd working space (see emacs+lsp) .cache # typical cmake build directory (source-tree-nephew) -build*/* +.build* # symlink to builddir/compile_commands.json; should be set manually in dev sandbox compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index e7c1b6eb..de455b56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,7 @@ enable_language(CXX) #message(STATUS "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}") # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From c0ff16e3acca3b0e5c22360b3bd5d5edc5cbba57 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:27:50 -0400 Subject: [PATCH 0438/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 4 +--- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index e90fd723..b0029e6b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # clangd (run via lsp) keeps state here .cache # typical build directory -build +.build # compile_commands.json: symlink to build directory, should be created manually compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 8506e5b6..52606f5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,9 +5,7 @@ cmake_minimum_required(VERSION 3.10) project(randomgen VERSION 0.1) enable_language(CXX) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) -#include(cmake/cxx.cmake) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From faf58781e9e7a45f7f2edf23eb52ae5d28384c03 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:28:45 -0400 Subject: [PATCH 0439/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 9 +++++---- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 9e716afc..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -# lsp keeps state here +# clangd working space (see emacs+lsp) .cache -# typical build directories -build -ccov +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index f336712c..ff3d8726 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(xo_ordinaltree VERSION 0.1) enable_language(CXX) # common XO macros (see github:Rconybea/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From f81b6abda80a87693494378c4936a8bc977c7b68 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:29:24 -0400 Subject: [PATCH 0440/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 9 ++++++--- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 49f711e2..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ -# typical build directories -build -ccov +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b8e6720..e412e4a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(xo_pyutil VERSION 0.1) enable_language(CXX) # common XO cmake macros (see github:Rconybea/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) # very little to unit test here +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From fd279f0e38601c288451354cf5d052c7ce3d9c0f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:30:07 -0400 Subject: [PATCH 0441/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 4 +--- README.md | 36 ++++++++++++++++++++++----------- cmake/xo-bootstrap-macros.cmake | 12 +++++++++++ 4 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index f52f1311..53a9c92f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # lsp keeps state here .cache # typical build directory -build +.build* # lsp: symlink to file in build directory (established manually) compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index b2e5ca94..5d84942e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,9 +5,7 @@ cmake_minimum_required(VERSION 3.10) project(xo_pyreflect VERSION 0.1) enable_language(CXX) -# common XO cmake macros (see github.com:Rconybea/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/README.md b/README.md index 7be07dd9..078e1a1b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# python bindings for c++ reflection library (xo-reflect) +# python bindings for c++ reflection library (xo-pyreflect) ## Getting Started @@ -22,6 +22,22 @@ $ make install ``` (also see .github/workflows/main.yml) +## Examples + +Assumes `xo-pyreflect` installed to `~/local2/lib` + +``` +PYTHONPATH=~/local2/lib python +>>> import xo_pyreflect +>>> dir(xo_pyreflect) +['SelfTagging', 'TaggedRcptr', 'TypeDescr', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] +>>> xo_pyreflect.TypeDescr.print_reflected_types() + +``` +(Not _immediately_ interesting: no reflected types in `pyreflect` itself) + +## Development + ### build for unit test coverage ``` $ cd xo-pyreflect @@ -37,24 +53,20 @@ $ cmake \ ### LSP (language server) support LSP looks for compile commands in the root of the source tree; -while Cmake creates them in the root of its build directory. +while `cmake` creates them in the root of its build directory. ``` $ cd xo-pyreflect $ ln -s build/compile_commands.json # supply compile commands to LSP ``` -## Examples +### display cmake variables -Assumes `xo-pyreflect` installed to `~/local2/lib` +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text ``` -PYTHONPATH=~/local2/lib python ->>> import pyreflect ->>> dir(pyreflect) -['SelfTagging', 'TaggedRcptr', 'TypeDescr', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] ->>> pyreflect.TypeDescr.print_reflected_types() - +$ cd xo-pyprintjson/build +$ cmake -LAH ``` - -Not _immediately_ interesting: no reflected types in `pyreflect` itself diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 80f8b7a96eff7f5509cd3956a9385b2b2755c9f6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:30:44 -0400 Subject: [PATCH 0442/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 6 +++++- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index d6536bad..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ -build +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 153acdac..c6c5fcb1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,7 @@ cmake_minimum_required(VERSION 3.10) project(xo_distribution VERSION 1.0) -# common XO cmake macros (see github.com:Rconybea/xo-cmake) -include(xo_macros/xo-project-macros) +include(cmake/xo-bootstrap-macros.cmake) xo_cxx_toplevel_options() diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From b07ce785c5d51c6aa9a0d452dab674f5ee1c944f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:31:30 -0400 Subject: [PATCH 0443/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 6 +++++- CMakeLists.txt | 2 +- README.md | 6 +++--- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ src/pydistribution/CMakeLists.txt | 2 +- 5 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index d6536bad..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ -build +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index d2823501..7b4de072 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.10) project(xo_pydistribution VERSION 1.0) -include(xo_macros/xo-project-macros) +include(cmake/xo-bootstrap-macros.cmake) xo_cxx_toplevel_options() diff --git a/README.md b/README.md index ef597486..98199fef 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,10 @@ Assumes `xo-pydistribution` installed to `~/local2/lib` ``` PYTHONPATH=~/local2/lib python ->>> import pydistribution ->>> dir(pydistribution) +>>> import xo_pydistribution +>>> dir(xo_pydistribution) ['Distribution', 'ExplicitDist', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'normalcdf'] ->>> from pydistribution import * +>>> from xo_pydistribution import * ``` normal distribution diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/src/pydistribution/CMakeLists.txt b/src/pydistribution/CMakeLists.txt index 8adaad7e..356ba92e 100644 --- a/src/pydistribution/CMakeLists.txt +++ b/src/pydistribution/CMakeLists.txt @@ -1,6 +1,6 @@ # xo_pydistribution/src/pydistribution/CMakeLists.txt -set(SELF_LIB pydistribution) +set(SELF_LIB xo_pydistribution) set(SELF_SRCS pydistribution.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) From 90c20ddae2c93aa514e67ad0d473957f7c2b0832 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:32:04 -0400 Subject: [PATCH 0444/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 7 ++++++- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 378eac25..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -build +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 68eb1445..1732dfa4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,7 @@ cmake_minimum_required(VERSION 3.10) project(xo_statistics VERSION 1.0) enable_language(CXX) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 884eb3acc41e80a580313c0b6b4a04f0c0e712f3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:32:51 -0400 Subject: [PATCH 0445/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 3 +-- CMakeLists.txt | 5 ++--- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 2323db6f..7d076252 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # lsp keeps state here .cache # typical build directories -build -ccov +.build* # compile_commands.json symlink -> build/compile_commands.json should be created manually compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 99aa239f..b12b9f56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,9 +5,8 @@ cmake_minimum_required(VERSION 3.10) project(printjson VERSION 0.1) enable_language(CXX) -# common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +# common XO cmake macros (see https://github.com/rconybea/xo-cmake) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From a96142ffea81ef2ce8da64e18d03b8de4f929dfa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:33:37 -0400 Subject: [PATCH 0446/2524] build: streamline CMAKE_MODULE_PATH interaction + README.md xpand --- .gitignore | 9 ++++----- CMakeLists.txt | 3 +-- README.md | 12 ++++++++++++ cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ include/README.md | 1 + include/xo/pyprintjson/pyprintjson.hpp | 25 ------------------------- 6 files changed, 30 insertions(+), 32 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake create mode 100644 include/README.md delete mode 100644 include/xo/pyprintjson/pyprintjson.hpp diff --git a/.gitignore b/.gitignore index 8ea1f615..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ -# lsp keep state here +# clangd working space (see emacs+lsp) .cache -# typical build directories -build -ccov -# for lsp: manual symlink to chosen build directory +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f326e13..603f7253 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(xo_pyprintjson VERSION 1.0) enable_language(CXX) # common XO cmake macros (see github.com:Rconybea/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/README.md b/README.md index 34b77909..e85bc227 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,18 @@ $ make install ``` (also see .github/workflows/main.yml) +## Examples + +``` +PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> import xo_pyprintjson +>>> dir(xo_pyprintjson) +['PrintJson', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] +>>> dir(xo_pyprintjson.PrintJson) +['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'instance', 'print'] +>>> +``` + ## Development ### build for unit test coverage diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..5d2b4c67 --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future pyprintjson #include files diff --git a/include/xo/pyprintjson/pyprintjson.hpp b/include/xo/pyprintjson/pyprintjson.hpp deleted file mode 100644 index c5fa007d..00000000 --- a/include/xo/pyprintjson/pyprintjson.hpp +++ /dev/null @@ -1,25 +0,0 @@ -/* @file pyprintjson.hpp - * - * automatically generated from src/pyprintjson/pyprintjson.hpp.in - * see src/pyprintjson/CMakeLists.txt - */ - -/* python requires module name = library name - * example: - * PYBIND11_MODULE(PYPRINTJSON_MODULE_NAME(), m) { ... } - */ -#define PYPRINTJSON_MODULE_NAME() pyprintjson - -/* example: - * py::module_::import(PYPRINTJSON_MODULE_NAME_STR) - */ -#define PYPRINTJSON_MODULE_NAME_STR "pyprintjson" - -/* example: - * PYPRINTJSON_IMPORT_MODULE() - * replaces - * py::module_::import("pyprintjson") - */ -#define PYPRINTJSON_IMPORT_MODULE() py::module_::import("pyprintjson") - -/* end pyprintjson.hpp */ From 9e69bd6ad71606059cb537b3ef47f680866961e2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:34:30 -0400 Subject: [PATCH 0447/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 3 +-- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index e9746d99..132925c0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,4 @@ compile_commands.json # lsp keeps state here .cache # typical build directories -build -ccov +.build* diff --git a/CMakeLists.txt b/CMakeLists.txt index 52296781..c6e1c8bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(callback VERSION 0.1) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup (for consistency with other xo libraries. no unit tests as of 10oct2023) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 260f3435e1f06a4b95a3af41e99a6ac543ea4909 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:35:02 -0400 Subject: [PATCH 0448/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 3e50f879..b1adb07b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ # symlink -> build/compile_commands.json should be created manually compile_commands.json # typical build directories -build +.build* diff --git a/CMakeLists.txt b/CMakeLists.txt index b63e290e..94fd52ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(webutil VERSION 1.0) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup (no unit tests yet, but want 'make tests' to do something) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 9893b7148388be2a2bfc5bd3ada6bbd31df7e12f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:35:34 -0400 Subject: [PATCH 0449/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 3 +-- README.md | 11 +++++++++++ cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index f52f1311..53a9c92f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # lsp keeps state here .cache # typical build directory -build +.build* # lsp: symlink to file in build directory (established manually) compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index ed35dc91..22a91f72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(xo_pywebutil VERSION 0.1) enable_language(CXX) # common XO cmake macros (see github.com:Rconybea/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/README.md b/README.md index 293fa8a4..52d702da 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,17 @@ $ make install ``` (also see .github/workflows/main.yml) +## Examples + +Assumes `xo-pywebutil` installed to `~/local2/lib` +``` +$ PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> import xo_pywebutil +>>> dir(xo_pywebutil) +['EndpointDescr', 'StreamEndpointDescr', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] +>>> from xo_pywebutil import * +``` + ## Development ### build for unit test coverage diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 421e786e1d7403341c5cd08100769e135a7569b1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:36:14 -0400 Subject: [PATCH 0450/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 3 +-- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 8ea1f615..f57a21b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # lsp keep state here .cache # typical build directories -build -ccov +.build* # for lsp: manual symlink to chosen build directory compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index b24e139a..3c4d8f9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(reactor VERSION 0.1) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 20475c59e194b2fd1a73779cdfd7193273d2d3e4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:37:32 -0400 Subject: [PATCH 0451/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 3 +-- EXAMPLES | 12 ++++++++++++ README.md | 11 +++++++++++ cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 5 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 EXAMPLES create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index f52f1311..53a9c92f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # lsp keeps state here .cache # typical build directory -build +.build* # lsp: symlink to file in build directory (established manually) compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index aec8d379..773425bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(xo_pyreactor VERSION 1.0) enable_language(CXX) # common XO cmake macros (see github.com:Rconybea/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/EXAMPLES b/EXAMPLES new file mode 100644 index 00000000..4b67a31a --- /dev/null +++ b/EXAMPLES @@ -0,0 +1,12 @@ +* see ../process_py/README + +>>> import reactor_py +>>> p=reactor_py.make_realization_printer() +>>> p + +>>> + +>>> import inspect +>>> inspect.getmro(reactor_py.SinkToConsole) + +>>> dir(reactor_py) diff --git a/README.md b/README.md index f618c641..bda29b5f 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,17 @@ $ make install ``` (also see .github/workflows/main.yml) +## Examples + +Assumes `xo-pyreactor` installed to `~/local2/lib` +``` +PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> import xo_pyreactor +>>> dir(xo_pyreactor) +['AbstractEventProcessor', 'AbstractEventStore', 'AbstractSink', 'AbstractSource', 'CallbackId', 'Reactor', 'ReactorSource', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'time2str'] +>>> +``` + ## Development ### build for unit test coverage diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 69d30651cceadca6cef8fb5c0056f71446b88cb3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:38:10 -0400 Subject: [PATCH 0452/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 3 +-- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ src/simulator/CMakeLists.txt | 2 -- 4 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 8ea1f615..f57a21b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # lsp keep state here .cache # typical build directories -build -ccov +.build* # for lsp: manual symlink to chosen build directory compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 82d94e6b..b9020fd0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(simulator VERSION 1.0) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/src/simulator/CMakeLists.txt b/src/simulator/CMakeLists.txt index 3a7879f5..2e8aaf73 100644 --- a/src/simulator/CMakeLists.txt +++ b/src/simulator/CMakeLists.txt @@ -15,5 +15,3 @@ xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 $ # in xo-simulator/cmake/simulatorConfig.cmake.in # xo_dependency(${SELF_LIB} reactor) -#xo_dependency(${SELF_LIB} webutil) -#xo_dependency(${SELF_LIB} callback) From 99bfbeb7f32460acd2f2a2658d22431c39eb1f59 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:39:05 -0400 Subject: [PATCH 0453/2524] pysimulator: initial commit --- .gitignore | 6 +++ CMakeLists.txt | 44 ++++++++++++++++++ README.md | 70 +++++++++++++++++++++++++++++ cmake/xo_pysimulatorConfig.cmake.in | 4 ++ include/README.md | 2 + src/pysimulator/CMakeLists.txt | 8 ++++ src/pysimulator/pysimulator.cpp | 66 +++++++++++++++++++++++++++ src/pysimulator/pysimulator.hpp.in | 25 +++++++++++ 8 files changed, 225 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/xo_pysimulatorConfig.cmake.in create mode 100644 include/README.md create mode 100644 src/pysimulator/CMakeLists.txt create mode 100644 src/pysimulator/pysimulator.cpp create mode 100644 src/pysimulator/pysimulator.hpp.in diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..53a9c92f --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# lsp keeps state here +.cache +# typical build directory +.build* +# lsp: symlink to file in build directory (established manually) +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..25398891 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,44 @@ +# xo-pysimulator/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pysimulator VERSION 0.1) +enable_language(CXX) + +# common XO cmake macros (see https://github.com:rconybea/xo-cmake.git) +include(xo_macros/xo-project-macros) + +# ---------------------------------------------------------------- +# 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 (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- +# sources + +add_subdirectory(src/pysimulator) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + diff --git a/README.md b/README.md new file mode 100644 index 00000000..676fe720 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# python bindings for c++ reflection library (xo-pysimulator) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-simulator](https://github.com/Rconybea/xo-simulator) +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-pyreflect](https://github.com/Rconybea/xo-pyreflect) +- [github/Rconybea/xo-pyprintjson](https://github.com/Rconybea/xo-pyprintjson) +- [github/Rconybea/xo-pyreactor](https://github.com/Rconybea/xo-pyreactor) + +### build + install +``` +$ cd xo-pysimulator +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +## Examples + +Assumes `xo-pysimulator` installed to `~/local2/lib` +``` +PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> import xo_pysimulator +>>> dir(xo_pysimulator) +['Simulator', 'SourceTimestamp', 'TimeSlip', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'make_simulator'] +>>> + +## Development + +### build for unit test coverage +``` +$ cd xo-pysimulator +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while `cmake` creates them in the root of its build directory. + +``` +$ cd xo-pysimulator +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +## Examples + +Assumes `xo-pysimulator` installed to `~/local2/lib` + +``` +PYTHONPATH=~/local2/lib python +>>> import pysimulator +>>> dir(pysimulator) +``` diff --git a/cmake/xo_pysimulatorConfig.cmake.in b/cmake/xo_pysimulatorConfig.cmake.in new file mode 100644 index 00000000..9c15f36a --- /dev/null +++ b/cmake/xo_pysimulatorConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..12c02f54 --- /dev/null +++ b/include/README.md @@ -0,0 +1,2 @@ +placeholder for future pysimulator #include files + diff --git a/src/pysimulator/CMakeLists.txt b/src/pysimulator/CMakeLists.txt new file mode 100644 index 00000000..c88c44d0 --- /dev/null +++ b/src/pysimulator/CMakeLists.txt @@ -0,0 +1,8 @@ +# xo_pysimulator/src/pysimulator/CMakeLists.txt + +set(SELF_LIB xo_pysimulator) +set(SELF_SRCS pysimulator.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} simulator) +xo_pybind11_dependency(${SELF_LIB} xo_pyutil) diff --git a/src/pysimulator/pysimulator.cpp b/src/pysimulator/pysimulator.cpp new file mode 100644 index 00000000..c1077b0a --- /dev/null +++ b/src/pysimulator/pysimulator.cpp @@ -0,0 +1,66 @@ +/* @file pysimulator.cpp */ + +#include "pysimulator.hpp" +#include "xo/pyreactor/pyreactor.hpp" + +#include "xo/simulator/Simulator.hpp" +#include "xo/simulator/SourceTimestamp.hpp" +#include "xo/simulator/TimeSlip.hpp" +#include "xo/pyutil/pyutil.hpp" +//#include +//#include +//#include + +namespace xo { + using xo::reactor::Reactor; + //using xo::scope; + namespace py = pybind11; + + namespace sim { + PYBIND11_MODULE(PYSIMULATOR_MODULE_NAME(), m) { + /* e.g. for Reactor */ + PYREACTOR_IMPORT_MODULE(); //py::module_::import("pyreactor") + + /* module docstring */ + m.doc() = "pybind11 plugin for xo.simulator"; + + m.def("make_simulator", + []() { + return xo::sim::Simulator::make(xo::time::timeutil::epoch()); + }, + "create new Simulator instance"); + + py::class_(m, "TimeSlip") + .def_property_readonly("sim_tm", &TimeSlip::sim_tm) + .def_property_readonly("real_tm", &TimeSlip::real_tm) + // .def("__repr__", &TimeSlip::display_string) // TODO + ; + + py::class_>(m, "Simulator") + .def_static("make", []() { return xo::sim::Simulator::make(xo::time::timeutil::epoch()); }) + .def_property_readonly("start_tm", &Simulator::t0) + .def_property_readonly("last_tm", &Simulator::last_tm) + .def_property_readonly("n_event", &Simulator::n_event) + .def_property_readonly("is_exhausted", &Simulator::is_exhausted) + .def("next_tm", &Simulator::next_tm) + .def("next_src", &Simulator::next_src) + .def("timeslip", &Simulator::timeslip) + .def("throttled_event_dt", &Simulator::throttled_event_dt) + .def("heap_contents", &Simulator::heap_contents) + .def("log_heap_contents", + [](Simulator & self) { + scope log(XO_LITERAL(log_level::always, "pysimulator", ".log_heap_contents")); + self.log_heap_contents(&log); + }) + .def("__repr__", &Simulator::display_string); + + py::class_(m, "SourceTimestamp") + .def("__repr__", &SourceTimestamp::display_string); + + } /*pysimulator*/ + } /*namespace sim*/ +} /*namespace xo*/ + +/* end pysimulator.cpp */ diff --git a/src/pysimulator/pysimulator.hpp.in b/src/pysimulator/pysimulator.hpp.in new file mode 100644 index 00000000..db3656c5 --- /dev/null +++ b/src/pysimulator/pysimulator.hpp.in @@ -0,0 +1,25 @@ +/* @file pysimulator.hpp + * + * automatically generated from src/xo_pysimulator/pysimulator.hpp.in + * see src/xo_pysimulator/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(PYSIMULATOR_MODULE_NAME(), m) { ... } + */ +#define PYSIMULATOR_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(PYSIMULATOR_MODULE_NAME_STR) + */ +#define PYSIMULATOR_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * PYSIMULATOR_IMPORT_MODULE() + * replaces + * py::module_::import("pysimulator") + */ +#define PYSIMULATOR_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pysimulator.hpp */ From 1b00b98a8f719ea45fd4b44df2c6618caf1f145f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:39:50 -0400 Subject: [PATCH 0454/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index eff45bd2..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # clangd working space (see emacs+lsp) .cache # typical cmake build directory (source-tree-nephew) -build*/* +.build* # symlink to builddir/compile_commands.json; should be set manually in dev sandbox compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d562914..0a10d628 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(process VERSION 1.0) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 2f908b7c3638bd66a926626a4d94c26b2f848752 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:40:26 -0400 Subject: [PATCH 0455/2524] build: bugfix SELF_LIB consistency + streamline CMAKE_MODULE_PATH --- .gitignore | 2 +- CMakeLists.txt | 2 +- README.md | 26 +++++++++++++++++++++++++- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ src/pyprocess/CMakeLists.txt | 2 +- src/pyprocess/pyprocess.hpp.in | 6 +++--- 6 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index f52f1311..53a9c92f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # lsp keeps state here .cache # typical build directory -build +.build* # lsp: symlink to file in build directory (established manually) compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 36786fb8..d53e2ca6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.10) project(xo_pyprocess VERSION 0.1) -include(xo_macros/xo-project-macros) +include(cmake/xo-bootstrap-macros.cmake) xo_cxx_toplevel_options() diff --git a/README.md b/README.md index 2ad68ffb..07725a19 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,17 @@ # python bindings for c++ stochastic process library (xo-process) -# build + install +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-process](https://github.com/Rconybea/xo-process) +- [github/Rconybea/xo-reactor](https://github.com/Rconybea/xo-reactor) +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-pyreflect](https://github.com/Rconybea/xo-pyreflect) +- [github/Rconybea/xo-pyprintjson](https://github.com/Rconybea/xo-pyprintjson) +- [github/Rconybea/xo-pyreactor](https://github.com/Rconybea/xo-pyreactor) + +### build + install ``` $ cd xo-pyprocess $ mkdir build @@ -15,6 +26,19 @@ $ make install ``` (also see .github/workflows/main.yml) +## Examples + +Assumes `xo-pyprocess` installed to `~/local2/lib` +``` +PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> import xo_pyprocess +>>> dir(xo_pyprocess) +['BrownianMotion', 'ExpProcess', 'RealizationSource', 'RealizationTracer', 'StochasticProcess', 'UpxAdapterSink', 'UpxEvent', 'UpxEventStore', 'UpxToConsole', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'make_brownian_motion', 'make_exponential_brownian_motion', 'make_realization_printer', 'make_realization_source', 'make_tracer'] +>>> +``` + +## Development + # build for unit test coverage ``` $ cd xo-pyprocess diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/src/pyprocess/CMakeLists.txt b/src/pyprocess/CMakeLists.txt index cf35ab2e..87a23e90 100644 --- a/src/pyprocess/CMakeLists.txt +++ b/src/pyprocess/CMakeLists.txt @@ -1,6 +1,6 @@ # xo_pyprocess/src/pyprocess/CMakeLists.txt -set(SELF_LIB pyprocess) +set(SELF_LIB xo_pyprocess) set(SELF_SRCS pyprocess.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) diff --git a/src/pyprocess/pyprocess.hpp.in b/src/pyprocess/pyprocess.hpp.in index d2f5cec1..938bf0c9 100644 --- a/src/pyprocess/pyprocess.hpp.in +++ b/src/pyprocess/pyprocess.hpp.in @@ -8,18 +8,18 @@ * example: * PYBIND11_MODULE(PYPROCESS_MODULE_NAME(), m) { ... } */ -#define PYPROCESS_MODULE_NAME() @SELF_LIBRARY_NAME@ +#define PYPROCESS_MODULE_NAME() @SELF_LIB@ /* example: * py::module_::import(PYPROCESS_MODULE_NAME_STR) */ -#define PYPROCESS_MODULE_NAME_STR "@SELF_LIBRARY_NAME@" +#define PYPROCESS_MODULE_NAME_STR "@SELF_LIB@" /* example: * PYPROCESS_IMPORT_MODULE() * replaces * py::module_::import("pyprocess") */ -#define PYPROCESS_IMPORT_MODULE() py::module_::import("@SELF_LIBRARY_NAME@") +#define PYPROCESS_IMPORT_MODULE() py::module_::import("@SELF_LIB@") /* end pyprocess.hpp */ From 4b2a991dc661b1c466fafe9b5ffdd12c06a32d3d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:41:08 -0400 Subject: [PATCH 0456/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 7 ++++++- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 378eac25..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -build +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index f4120636..3c83fec9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(xo_kalmanfilter VERSION 1.0) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From b5f6ede37758a4c3865eef53fd6a2172da0a699f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:41:33 -0400 Subject: [PATCH 0457/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 7 +++- CMakeLists.txt | 2 +- README.md | 70 +++++++++++++++++++++++++++++++++ cmake/xo-bootstrap-macros.cmake | 12 ++++++ 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 README.md create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index 378eac25..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -build +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a1602f6..d044769a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.10) project(xo_pykalmanfilter VERSION 1.0) -include(xo_macros/xo-project-macros) +include(cmake/xo-bootstrap-macros.cmake) xo_cxx_toplevel_options() diff --git a/README.md b/README.md new file mode 100644 index 00000000..7d7d1b11 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# python bindings for c++ kalman filter library (xo-kalmanfilter) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-kalmanfilter](https://github.com/Rconybea/xo-kalmanfilter) +- [github/Rconybea/xo-pyreactor](https://github.com/Rconybea/xo-pyreactor) + +### build + install + +``` +$ cd xo-pykalmanfilter +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +## Examples + +Assumes `xo-pykalmanfilter` installed to `~/local2/lib` +``` +PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> import xo_pykalmanfilter +>>> dir(xo_pykalmanfilter) +['KalmanFilter', 'KalmanFilterInput', 'KalmanFilterInputToConsole', 'KalmanFilterObservable', 'KalmanFilterSpec', 'KalmanFilterState', 'KalmanFilterStateEventStore', 'KalmanFilterStateExt', 'KalmanFilterStateToConsole', 'KalmanFilterStep', 'KalmanFilterSvc', 'KalmanFilterTransition', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'kf_engine_correct', 'kf_engine_correct1', 'kf_engine_extrapolate', 'kf_engine_gain', 'kf_engine_gain1', 'make_kalman_filter', 'make_kalman_filter_input', 'make_kalman_filter_input_printer', 'make_kalman_filter_state_printer', 'print_matrix', 'print_vector'] +>>> +``` + +## Development + +### build for unit test coverage +``` +$ cd xo-pykalmanfilter +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pykalmanfilter +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-pykalmanfilter/build +$ cmake -LAH +``` diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From 0f7f6d0e1ce61d602fae5afa8e3d27064d8d9d4a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:42:04 -0400 Subject: [PATCH 0458/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 2 +- CMakeLists.txt | 3 +-- cmake/xo-bootstrap-macros.cmake | 12 ++++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index eff45bd2..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # clangd working space (see emacs+lsp) .cache # typical cmake build directory (source-tree-nephew) -build*/* +.build* # symlink to builddir/compile_commands.json; should be set manually in dev sandbox compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 2178a12d..e3d66093 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(websock VERSION 1.0) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) -include(xo_macros/xo_cxx) -include(xo_macros/code-coverage) +include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- # unit test setup diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) From f7abb8fedba3f1ae98ae9f2af7c9ca6908dba824 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:42:35 -0400 Subject: [PATCH 0459/2524] build: streamline CMAKE_MODULE_PATH interaction --- .gitignore | 6 ++- CMakeLists.txt | 3 +- README.md | 71 +++++++++++++++++++++++++++++++++ cmake/xo-bootstrap-macros.cmake | 12 ++++++ src/pywebsock/CMakeLists.txt | 2 +- 5 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 README.md create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/.gitignore b/.gitignore index d6536bad..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ -build +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 20774e1c..56e9f2eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,9 @@ # xo-pywebsock/CMakeLists.txt cmake_minimum_required(VERSION 3.10) - project(xo_pywebsock VERSION 1.0) -include(xo_macros/xo-project-macros) +include(cmake/xo-bootstrap-macros.cmake) xo_cxx_toplevel_options() diff --git a/README.md b/README.md new file mode 100644 index 00000000..468c605d --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# python bindings for c++ websocket library (xo-websock) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/xo-websock](https://github.com/Rconybea/xo-websock) +- [github/Rconybea/xo-pyutil](https://github.com/Rconybea/xo-pyutil) +- [github/Rconybea/xo-pyreactor](https://github.com/Rconybea/xo-pyreactor) + +### build + install + +``` +$ cd xo-pywebsock +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer, e.g. ~/local +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +## Examples + +Assumes `xo-pywebsock` installed to `~/local2/lib` +``` +PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> import xo_pywebsock +>>> dir(xo_pywebsock) +['Runstate', 'Webserver', 'WebserverConfig', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'make_webserver'] +>>> +``` + +## Development + +### build for unit test coverage +``` +$ cd xo-pywebsock +$ mkdir build-ccov +$ cd build-ccov +$ cmake \ + -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake \ + -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} \ + -DCODE_COVERAGE=ON \ + -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pywebsock +$ ln -s build/compile_commands.json # supply compile commands to LSP +``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-pywebsock/build +$ cmake -LAH +``` diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..16644435 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,12 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/src/pywebsock/CMakeLists.txt b/src/pywebsock/CMakeLists.txt index 57c5d75a..1e40a064 100644 --- a/src/pywebsock/CMakeLists.txt +++ b/src/pywebsock/CMakeLists.txt @@ -1,6 +1,6 @@ # xo_pywebsock/src/pywebsock/CMakeLists.txt -set(SELF_LIB pywebsock) +set(SELF_LIB xo_pywebsock) set(SELF_SRCS pywebsock.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) From 45d718820aace6f9a1d373c64f43c0bec18164b0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:57:00 -0400 Subject: [PATCH 0460/2524] github: initial workflow (wip -- broken) --- .github/workflows/main.yml | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..8223aab6 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,63 @@ +name: build xo-pyreflect + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + - name: Install pybind11-dev + run: sudo apt-get install -y pybind11-dev + + # ---------------------------------------------------------------- + + - name: clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Configure self (pysimulator) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_pysimulator -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (pysimulator) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_pysimulator --config ${{env.BUILD_TYPE}} + + - name: Test self (pysimulator) + working-directory: ${{github.workspace}}/build_pysimulator + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} From 9430b4ee59b13f56383ca075bdd0fc6a37ac2d5a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 19:59:55 -0400 Subject: [PATCH 0461/2524] workflow: streamlining (wip - broken) --- .github/workflows/main.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8223aab6..e6ad516f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,12 +21,16 @@ jobs: - name: checkout source uses: actions/checkout@v3 - - name: Install catch2 - # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] - run: sudo apt-get install -y catch2 + - name: Install dependencies + run: | + echo "::group::install catch2" + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + sudo apt-get install -y catch2 + echo "::endgroup" - - name: Install pybind11-dev - run: sudo apt-get install -y pybind11-dev + echo "::group::install pybind11" + run: sudo apt-get install -y pybind11-dev + echo "::endgroup" # ---------------------------------------------------------------- @@ -35,9 +39,8 @@ jobs: with: repository: Rconybea/xo-cmake path: repo/xo-cmake - - - name: Configure xo-cmake - run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + run: | + cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake - name: Build xo-cmake (trivial) run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} From 9c36ab7e2eb593f449ddd0d18109949334da79be Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:02:25 -0400 Subject: [PATCH 0462/2524] workflow: streamline xo-cmake prep --- .github/workflows/main.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e6ad516f..d6a7adae 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,14 +39,20 @@ jobs: with: repository: Rconybea/xo-cmake path: repo/xo-cmake + + - name: build xo-cmake run: | + echo "::group::configure xo-cmake" cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + echo "::endgroup" - - name: Build xo-cmake (trivial) - run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + echo "::group::compile xo-cmake" + cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + echo "::endgroup" - - name: Install xo-cmake - run: cmake --install ${{github.workspace}}/build_xo-cmake + echo "::group::local install xo-cmake" + cmake --install ${{github.workspace}}/build_xo-cmake + echo "::endgroup" # ---------------------------------------------------------------- From 3817677fc8372480ea64e9dd49e6d9c1cc38631b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:03:18 -0400 Subject: [PATCH 0463/2524] workflow: bugfix: typo in apt-get for pybind11-dev --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d6a7adae..4aafb3b2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: echo "::endgroup" echo "::group::install pybind11" - run: sudo apt-get install -y pybind11-dev + sudo apt-get install -y pybind11-dev echo "::endgroup" # ---------------------------------------------------------------- From 4693064fd3b3f1ecce7e6adf8041a1527e8291e9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:11:39 -0400 Subject: [PATCH 0464/2524] workflow: + build xo-simulator (wip - broken) --- .github/workflows/main.yml | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4aafb3b2..95a81b40 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,8 @@ jobs: - name: Install dependencies run: | echo "::group::install catch2" - # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + # install catch2. see + # [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] sudo apt-get install -y catch2 echo "::endgroup" @@ -54,8 +55,36 @@ jobs: cmake --install ${{github.workspace}}/build_xo-cmake echo "::endgroup" + echo "::group::local dir tree" + tree ${{github.workspace}}/local + echo "::endgroup" + # ---------------------------------------------------------------- + - name: clone xo-simulator + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-simulator + path: repo/xo-simulator + + - name: build xo-simulator + run: | + echo "::group::configure xo-simulator + cmake -B ${{github.workspace}}/build_xo-simulator -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-simulator + echo "::endgroup" + + echo "::group::compile xo-simulator" + cmake --build ${{github.workspace}}/build_xo-simulator --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install xo-simulator"; + cmake --install ${{github.workspace}}/build_xo-simulator + echo "::endgroup" + + echo "::group::local dir tree" + tree ${{github.workspace}//local + echo "::endgroup" + - name: Configure self (pysimulator) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 14cec30749dd882def5f8a071b9bf3b2fe9538f7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:12:41 -0400 Subject: [PATCH 0465/2524] workflow: bugfix: typo in .yml file --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 95a81b40..d64f7f5f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -69,7 +69,7 @@ jobs: - name: build xo-simulator run: | - echo "::group::configure xo-simulator + echo "::group::configure xo-simulator" cmake -B ${{github.workspace}}/build_xo-simulator -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-simulator echo "::endgroup" From 963ba96f6cd441c3833f2dd44eb49967eae20e57 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:13:20 -0400 Subject: [PATCH 0466/2524] workflow: bugfix: syntax! --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d64f7f5f..71a02ba7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -82,7 +82,7 @@ jobs: echo "::endgroup" echo "::group::local dir tree" - tree ${{github.workspace}//local + tree ${{github.workspace}}/local echo "::endgroup" - name: Configure self (pysimulator) From 1348ba81c89b68318538948f71ae1089158f5ca5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:16:02 -0400 Subject: [PATCH 0467/2524] workflow: build xo-reactor (wip - broken) --- .github/workflows/main.yml | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 71a02ba7..17bd0709 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -61,24 +61,29 @@ jobs: # ---------------------------------------------------------------- - - name: clone xo-simulator + - name: clone xo-reactor uses: actions/checkout@v3 with: - repository: Rconybea/xo-simulator - path: repo/xo-simulator + repository: Rconybea/xo-reactor + path: repo/xo-reactor - - name: build xo-simulator + - name: build xo-reactor run: | - echo "::group::configure xo-simulator" - cmake -B ${{github.workspace}}/build_xo-simulator -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-simulator + XONAME=xo-reactor + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::configure xo-reactor" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} echo "::endgroup" - echo "::group::compile xo-simulator" - cmake --build ${{github.workspace}}/build_xo-simulator --config ${{env.BUILD_TYPE}} + echo "::group::compile xo-reactor" + cmake --build ${{github.workspace}}/build_xo-reactor --config ${{env.BUILD_TYPE}} echo "::endgroup" - echo "::group::local install xo-simulator"; - cmake --install ${{github.workspace}}/build_xo-simulator + echo "::group::local install xo-reactor"; + cmake --install ${{github.workspace}}/build_xo-reactor echo "::endgroup" echo "::group::local dir tree" From b5590ce449713ed66dc47b798f0a75a261beedd0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:18:57 -0400 Subject: [PATCH 0468/2524] workflow: build xo-reflect (wip - broken) --- .github/workflows/main.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 17bd0709..672c052d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -61,33 +61,33 @@ jobs: # ---------------------------------------------------------------- - - name: clone xo-reactor + - name: clone xo-reflect uses: actions/checkout@v3 with: - repository: Rconybea/xo-reactor - path: repo/xo-reactor + repository: Rconybea/xo-reflect + path: repo/xo-reflect - - name: build xo-reactor + - name: build xo-reflect run: | - XONAME=xo-reactor + XONAME=xo-reflect XOSRC=repo/${XONAME} BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local - echo "::group::configure xo-reactor" + echo "::group::configure ${XONAME} cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} echo "::endgroup" - echo "::group::compile xo-reactor" - cmake --build ${{github.workspace}}/build_xo-reactor --config ${{env.BUILD_TYPE}} + echo "::group::compile ${XONAME} + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} echo "::endgroup" - echo "::group::local install xo-reactor"; - cmake --install ${{github.workspace}}/build_xo-reactor + echo "::group::local install ${XONAME}; + cmake --install ${BUILDDIR} echo "::endgroup" echo "::group::local dir tree" - tree ${{github.workspace}}/local + tree ${PREFIX} echo "::endgroup" - name: Configure self (pysimulator) From 8d83e37ac7e10df72535c9c3d45fc9ef426136b9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:20:25 -0400 Subject: [PATCH 0469/2524] workflow: bugfix: remote repo name --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 672c052d..47f656a6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,7 +64,7 @@ jobs: - name: clone xo-reflect uses: actions/checkout@v3 with: - repository: Rconybea/xo-reflect + repository: Rconybea/reflect # not xo-reflect ! path: repo/xo-reflect - name: build xo-reflect From 76c7733f7728bd398d96b6761ae1af16a641ed6c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:26:02 -0400 Subject: [PATCH 0470/2524] workflow: xo-simulator: debugging (wip - broken) --- .github/workflows/main.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 47f656a6..cb4c9442 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,7 +64,7 @@ jobs: - name: clone xo-reflect uses: actions/checkout@v3 with: - repository: Rconybea/reflect # not xo-reflect ! + repository: Rconybea/reflect path: repo/xo-reflect - name: build xo-reflect @@ -74,6 +74,10 @@ jobs: BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local + echo "::group::repo dir tree" + tree repo + echo "::endgroup" + echo "::group::configure ${XONAME} cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} echo "::endgroup" From 05357a639808806e30cd9d7cbf98944953fef46a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:28:08 -0400 Subject: [PATCH 0471/2524] workflow: bugfix: yaml typos! (wip - broken) --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cb4c9442..34526171 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -75,10 +75,10 @@ jobs: PREFIX=${{github.workspace}}/local echo "::group::repo dir tree" - tree repo + tree -L 2 repo echo "::endgroup" - echo "::group::configure ${XONAME} + echo "::group::configure ${XONAME}" cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} echo "::endgroup" @@ -86,7 +86,7 @@ jobs: cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} echo "::endgroup" - echo "::group::local install ${XONAME}; + echo "::group::local install ${XONAME}" cmake --install ${BUILDDIR} echo "::endgroup" From daa42fcaddc3a4f92a564233a0636e7b27afcdf9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:31:00 -0400 Subject: [PATCH 0472/2524] workflow: build xo-refcnt (wip - broken) --- .github/workflows/main.yml | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 34526171..5a9f6d1c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -43,33 +43,38 @@ jobs: - name: build xo-cmake run: | - echo "::group::configure xo-cmake" - cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + XONAME=xo-cmake + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::configure ${XONAME} + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} echo "::endgroup" - echo "::group::compile xo-cmake" + echo "::group::compile ${XONAME} cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} echo "::endgroup" - echo "::group::local install xo-cmake" + echo "::group::local install ${XONAME} cmake --install ${{github.workspace}}/build_xo-cmake echo "::endgroup" echo "::group::local dir tree" - tree ${{github.workspace}}/local + tree ${PREFIX} echo "::endgroup" # ---------------------------------------------------------------- - - name: clone xo-reflect + - name: clone xo-refcnt uses: actions/checkout@v3 with: - repository: Rconybea/reflect - path: repo/xo-reflect + repository: Rconybea/refcnt + path: repo/xo-refcnt - - name: build xo-reflect + - name: build xo-refcnt run: | - XONAME=xo-reflect + XONAME=xo-refcnt XOSRC=repo/${XONAME} BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local From c520ad82945a27617549bd7fa13fffcf0349677e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:31:37 -0400 Subject: [PATCH 0473/2524] workflow: bugfix: yaml typos! (wip - broken) --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5a9f6d1c..945c720c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,15 +48,15 @@ jobs: BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local - echo "::group::configure ${XONAME} + echo "::group::configure ${XONAME}" cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} echo "::endgroup" - echo "::group::compile ${XONAME} + echo "::group::compile ${XONAME}" cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} echo "::endgroup" - echo "::group::local install ${XONAME} + echo "::group::local install ${XONAME}" cmake --install ${{github.workspace}}/build_xo-cmake echo "::endgroup" From 99190e461551d06aa84e14af463f209472b297bc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:35:35 -0400 Subject: [PATCH 0474/2524] workflow: build indentlog (wip - broken) --- .github/workflows/main.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 945c720c..3ed07489 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -53,11 +53,11 @@ jobs: echo "::endgroup" echo "::group::compile ${XONAME}" - cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} echo "::endgroup" echo "::group::local install ${XONAME}" - cmake --install ${{github.workspace}}/build_xo-cmake + cmake --install ${BUILDDIR} echo "::endgroup" echo "::group::local dir tree" @@ -66,15 +66,15 @@ jobs: # ---------------------------------------------------------------- - - name: clone xo-refcnt + - name: clone xo-indentlog uses: actions/checkout@v3 with: - repository: Rconybea/refcnt - path: repo/xo-refcnt + repository: Rconybea/indentlog + path: repo/xo-indentlog - - name: build xo-refcnt + - name: build xo-indentlog run: | - XONAME=xo-refcnt + XONAME=xo-indentlog XOSRC=repo/${XONAME} BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local @@ -87,7 +87,7 @@ jobs: cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} echo "::endgroup" - echo "::group::compile ${XONAME} + echo "::group::compile ${XONAME}" cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} echo "::endgroup" From 3ed31ad824dc67afeab67559ed8ed52c1847852d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:38:01 -0400 Subject: [PATCH 0475/2524] workflow: back to xo-indentlog build (wip - broken) --- .github/workflows/main.yml | 39 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3ed07489..763f0782 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -88,7 +88,7 @@ jobs: echo "::endgroup" echo "::group::compile ${XONAME}" - cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j echo "::endgroup" echo "::group::local install ${XONAME}" @@ -96,7 +96,42 @@ jobs: echo "::endgroup" echo "::group::local dir tree" - tree ${PREFIX} + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/xo-refcnt + + - name: build xo-refcnt + run: | + XONAME=xo-refcnt + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} echo "::endgroup" - name: Configure self (pysimulator) From 6417e0e098fb87f1a34ff053399efcd96f7bfad4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:39:59 -0400 Subject: [PATCH 0476/2524] workflow: back to xo-reflect build (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 763f0782..610dfbe3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -134,6 +134,41 @@ jobs: tree -L 3 ${PREFIX} echo "::endgroup" + # ---------------------------------------------------------------- + + - name: clone xo-reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/xo-reflect + + - name: build xo-reflect + run: | + XONAME=xo-reflect + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + - name: Configure self (pysimulator) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 4c912d6fe02b6d210fe54cb4f3d03fc27c526cf3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:43:24 -0400 Subject: [PATCH 0477/2524] workflow: + xo-subsys build (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 610dfbe3..040ee495 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -136,6 +136,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/xo-subsys + + - name: build xo-subsys + run: | + XONAME=xo-subsys + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-reflect uses: actions/checkout@v3 with: From 1021889c01438a3279967f9042092927f17f6954 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:45:34 -0400 Subject: [PATCH 0478/2524] workflow: bugfix: return to xo-reactor build (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 040ee495..3aaa0a36 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -204,6 +204,41 @@ jobs: tree -L 3 ${PREFIX} echo "::endgroup" + # ---------------------------------------------------------------- + + - name: clone xo-reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/reactor + path: repo/xo-reactor + + - name: build xo-reactor + run: | + XONAME=xo-reactor + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + - name: Configure self (pysimulator) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From d0700cb3619b5c4ebef38a5a7caddaf9dd4e985a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:46:48 -0400 Subject: [PATCH 0479/2524] workflow: bugfix: fix xo-reactor repo name --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3aaa0a36..f420f343 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -209,7 +209,7 @@ jobs: - name: clone xo-reactor uses: actions/checkout@v3 with: - repository: Rconybea/reactor + repository: Rconybea/xo-reactor path: repo/xo-reactor - name: build xo-reactor From 3e7b9a000a66ecf1ac23cafa8daad0e101718e8f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:49:18 -0400 Subject: [PATCH 0480/2524] workflow: + xo-webutil (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f420f343..f760ece0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -206,6 +206,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/xo-webutil + + - name: build xo-webutil + run: | + XONAME=xo-webutil + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-reactor uses: actions/checkout@v3 with: From ee82037ba9ba9fd179ec70548742063fe2cc96d2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:51:07 -0400 Subject: [PATCH 0481/2524] workflow: + xo-callback (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f760ece0..e36d85a1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -206,6 +206,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/xo-callback + + - name: build xo-callback + run: | + XONAME=xo-callback + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-webutil uses: actions/checkout@v3 with: From 591b7388fed02b9946a11ee8513d7c3a1a9d1cd0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:53:31 -0400 Subject: [PATCH 0482/2524] workflow: + xo-printjson (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e36d85a1..e7eb52f0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -241,6 +241,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/xo-printjson + + - name: build xo-printjson + run: | + XONAME=xo-printjson + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-webutil uses: actions/checkout@v3 with: From aa77c5f4b493efae91433d0d507cf0c3dd4cdea5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:57:09 -0400 Subject: [PATCH 0483/2524] workflow: + xo-ordinaltree (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e7eb52f0..f1d95a42 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -276,6 +276,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/xo-ordinaltree + + - name: build xo-ordinaltree + run: | + XONAME=xo-ordinaltree + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-webutil uses: actions/checkout@v3 with: From b1e08d791a93087c66730f4794fb75b671125cd6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 20:59:09 -0400 Subject: [PATCH 0484/2524] workflow: + xo-randomgen (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f1d95a42..d0a7c5e2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -276,6 +276,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-randomgen + path: repo/xo-randomgen + + - name: build xo-randomgen + run: | + XONAME=xo-randomgen + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-ordinaltree uses: actions/checkout@v3 with: From 6276893847f2ab19743f512c32b2b4757c957955 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:02:50 -0400 Subject: [PATCH 0485/2524] workflow: bugfix: xo-randomgen repo name --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d0a7c5e2..6121a8d1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -279,7 +279,7 @@ jobs: - name: clone xo-randomgen uses: actions/checkout@v3 with: - repository: Rconybea/xo-randomgen + repository: Rconybea/randomgen path: repo/xo-randomgen - name: build xo-randomgen From 78bb709ddc2b3ac0c1737ce4734f1a9e3f020c67 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:06:04 -0400 Subject: [PATCH 0486/2524] workflow: + xo-simulator (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6121a8d1..7f4e8374 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -414,6 +414,41 @@ jobs: tree -L 3 ${PREFIX} echo "::endgroup" + # ---------------------------------------------------------------- + + - name: clone xo-simulator + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-simulator + path: repo/xo-simulator + + - name: build xo-simulator + run: | + XONAME=xo-simulator + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + - name: Configure self (pysimulator) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 204e3bae4f902de142cc2d14658239bafab88b43 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:11:08 -0400 Subject: [PATCH 0487/2524] workflow: + xo-pyutil (wip - broken) --- .github/workflows/main.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7f4e8374..03e17a9c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -449,6 +449,43 @@ jobs: tree -L 3 ${PREFIX} echo "::endgroup" + # ---------------------------------------------------------------- + + - name: clone xo-pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/xo-pyutil + + - name: build xo-pyutil + run: | + XONAME=xo-pyutil + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: Configure self (pysimulator) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 630ab081c62a510ebc2d70709496c1f894811861 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:15:18 -0400 Subject: [PATCH 0488/2524] build: report xo_pyreactor dependency --- src/pysimulator/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pysimulator/CMakeLists.txt b/src/pysimulator/CMakeLists.txt index c88c44d0..541c3378 100644 --- a/src/pysimulator/CMakeLists.txt +++ b/src/pysimulator/CMakeLists.txt @@ -4,5 +4,6 @@ set(SELF_LIB xo_pysimulator) set(SELF_SRCS pysimulator.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} xo_pyreactor) xo_pybind11_dependency(${SELF_LIB} simulator) xo_pybind11_dependency(${SELF_LIB} xo_pyutil) From a8161f301ee2f8e1d63c59fe41ba8f6d54ec5223 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:16:08 -0400 Subject: [PATCH 0489/2524] workflow: + xo-pyreactor (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 03e17a9c..17201d86 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -486,6 +486,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-pyreactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyreactor + path: repo/xo-pyreactor + + - name: build xo-pyreactor + run: | + XONAME=xo-pyreactor + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: Configure self (pysimulator) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 15f42c083a71d73ff6b7647cdabe1bf344928f23 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:19:33 -0400 Subject: [PATCH 0490/2524] workflow: + xo-pyprintjson (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 17201d86..e125db88 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -486,6 +486,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-pyprintjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyprintjson + path: repo/xo-pyprintjson + + - name: build xo-pyprintjson + run: | + XONAME=xo-pyprintjson + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-pyreactor uses: actions/checkout@v3 with: From 209f2be30432aceb666b0d14862d4b9e480ac9c9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:22:35 -0400 Subject: [PATCH 0491/2524] workflow: + xo-pyreflect (wip - broken) --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e125db88..2c07e63a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -521,6 +521,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-pyreflect + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyreflect + path: repo/xo-pyreflect + + - name: build xo-pyreflect + run: | + XONAME=xo-pyreflect + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-pyreactor uses: actions/checkout@v3 with: From 692078e1a0878d34bb5bdbea9ed57f5e98b87315 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:25:56 -0400 Subject: [PATCH 0492/2524] workflow: need xo-pyreflect before xo-pyprintjson (wip - broken) --- .github/workflows/main.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2c07e63a..6cf1b768 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -486,15 +486,15 @@ jobs: # ---------------------------------------------------------------- - - name: clone xo-pyprintjson + - name: clone xo-pyreflect uses: actions/checkout@v3 with: - repository: Rconybea/xo-pyprintjson - path: repo/xo-pyprintjson + repository: Rconybea/xo-pyreflect + path: repo/xo-pyreflect - - name: build xo-pyprintjson + - name: build xo-pyreflect run: | - XONAME=xo-pyprintjson + XONAME=xo-pyreflect XOSRC=repo/${XONAME} BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local @@ -521,15 +521,15 @@ jobs: # ---------------------------------------------------------------- - - name: clone xo-pyreflect + - name: clone xo-pyprintjson uses: actions/checkout@v3 with: - repository: Rconybea/xo-pyreflect - path: repo/xo-pyreflect + repository: Rconybea/xo-pyprintjson + path: repo/xo-pyprintjson - - name: build xo-pyreflect + - name: build xo-pyprintjson run: | - XONAME=xo-pyreflect + XONAME=xo-pyprintjson XOSRC=repo/${XONAME} BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local From 66abd390bd81607e06298f9581e2898cc6f5e4f2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 15 Mar 2024 21:34:20 -0400 Subject: [PATCH 0493/2524] workflow: streamline xo-pysimulator build step --- .github/workflows/main.yml | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6cf1b768..621046c5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -594,14 +594,27 @@ jobs: - name: Configure self (pysimulator) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build_pysimulator -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + run: | + XONAME=xo-pysimulator + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local - - name: Build self (pysimulator) - # Build your program with the given configuration - run: cmake --build ${{github.workspace}}/build_pysimulator --config ${{env.BUILD_TYPE}} + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" - - name: Test self (pysimulator) - working-directory: ${{github.workspace}}/build_pysimulator - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -C ${{env.BUILD_TYPE}} + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + (cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}}) From c8f17169d95504e89634bb4e57668fdcc65e2d49 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:04:46 -0400 Subject: [PATCH 0494/2524] xo-kalmanfilter: + README.md --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..fd4ef52d --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# kalman filter library + +linear kalman filter implementation. + +## Getting Started + +### build + install dependencies + +build+install these first + +- xo-reactor [github.com/Rconybea/xo-reactor](https://github.com/Rconybea/xo-reactor) + +See `.github/workflows/main.yml` in this repo for example build+install on ubuntu + +### build + install xo-kalmanfilter +``` +$ cd xo-kalmanfilter +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +### build for unit test coverage +``` +$ cd xo-kalmanfilter +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +## Development + +### LSP support + +LSP looks for compile commands in the root of the source tree; +cmake creates them in the root of its build directory. + +``` +$ cd xo-kalmanfilter +$ ln -s build/compile_commands.json +``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-kalmanfilter/build +$ cmake -LAH +``` From 637e0902437318a026a7f5015a7497bb09bf8b2d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:06:44 -0400 Subject: [PATCH 0495/2524] github: worfklow for ubuntu build --- .github/workflows/main.yml | 309 +++++++++++++++++++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..a34ea6aa --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,309 @@ +name: build xo-optionutil + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + echo "::group::install catch2" + # install catch2. see + # [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + sudo apt-get install -y catch2 + echo "::endgroup" + + #echo "::group::install pybind11" + #sudo apt-get install -y pybind11-dev + #echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: build xo-cmake + run: | + XONAME=xo-cmake + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/xo-indentlog + + - name: build xo-indentlog + run: | + XONAME=xo-indentlog + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/xo-refcnt + + - name: build xo-refcnt + run: | + XONAME=xo-refcnt + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/xo-subsys + + - name: build xo-subsys + run: | + XONAME=xo-subsys + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/xo-reflect + + - name: build xo-reflect + run: | + XONAME=xo-reflect + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + +# - name: clone xo-callback +# uses: actions/checkout@v3 +# with: +# repository: Rconybea/xo-callback +# path: repo/xo-callback +# +# - name: build xo-callback +# run: | +# XONAME=xo-callback +# XOSRC=repo/${XONAME} +# BUILDDIR=${{github.workspace}}/build_${XONAME} +# PREFIX=${{github.workspace}}/local +# +# echo "::group::repo dir tree" +# tree -L 2 repo +# echo "::endgroup" +# +# echo "::group::configure ${XONAME}" +# cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} +# echo "::endgroup" +# +# echo "::group::compile ${XONAME}" +# cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j +# echo "::endgroup" +# +# echo "::group::local install ${XONAME}" +# cmake --install ${BUILDDIR} +# echo "::endgroup" +# +# echo "::group::local dir tree" +# tree -L 3 ${PREFIX} +# echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/xo-printjson + + - name: build xo-printjson + run: | + XONAME=xo-printjson + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: Configure self (kalmanfilter) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: | + XONAME=xo-kalmanfilter + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + (cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}}) From ce1ed005a5f419d97176cf8a3944cbc0e49a5725 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:11:56 -0400 Subject: [PATCH 0496/2524] worfklow: + xo-reactor dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a34ea6aa..d99c8cb7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -276,6 +276,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/xo-reactor + + - name: build xo-reactor + run: | + XONAME=xo-reactor + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: Configure self (kalmanfilter) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 856e2f8de2a80f6b4408140601220632c00bcedd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:15:13 -0400 Subject: [PATCH 0497/2524] worfklow: + xo-webutil dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d99c8cb7..38bd87d6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -276,6 +276,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/xo-webutil + + - name: build xo-webutil + run: | + XONAME=xo-webutil + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-reactor uses: actions/checkout@v3 with: From 661f52373e9d3c1f447c27a60bcf01bcacdc030b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:17:33 -0400 Subject: [PATCH 0498/2524] worfklow: + xo-callback dep --- .github/workflows/main.yml | 64 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 38bd87d6..3060d6ab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -206,38 +206,38 @@ jobs: # ---------------------------------------------------------------- -# - name: clone xo-callback -# uses: actions/checkout@v3 -# with: -# repository: Rconybea/xo-callback -# path: repo/xo-callback -# -# - name: build xo-callback -# run: | -# XONAME=xo-callback -# XOSRC=repo/${XONAME} -# BUILDDIR=${{github.workspace}}/build_${XONAME} -# PREFIX=${{github.workspace}}/local -# -# echo "::group::repo dir tree" -# tree -L 2 repo -# echo "::endgroup" -# -# echo "::group::configure ${XONAME}" -# cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} -# echo "::endgroup" -# -# echo "::group::compile ${XONAME}" -# cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j -# echo "::endgroup" -# -# echo "::group::local install ${XONAME}" -# cmake --install ${BUILDDIR} -# echo "::endgroup" -# -# echo "::group::local dir tree" -# tree -L 3 ${PREFIX} -# echo "::endgroup" + - name: clone xo-callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/xo-callback + + - name: build xo-callback + run: | + XONAME=xo-callback + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" # ---------------------------------------------------------------- From 12df1e773bc9f28a57822378d7f769e56d948819 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:20:19 -0400 Subject: [PATCH 0499/2524] worfklow: + xo-ordinaltree dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3060d6ab..4de8815b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -311,6 +311,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/xo-ordinaltree + + - name: build xo-ordinaltree + run: | + XONAME=xo-ordinaltree + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-reactor uses: actions/checkout@v3 with: From 9157edb3f90d3a453de0166b9ecb71d56fe1d3be Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:24:34 -0400 Subject: [PATCH 0500/2524] workflows: + xo-randomgen dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4de8815b..e0d1e7d4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -311,6 +311,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-randomgen + path: repo/xo-randomgen + + - name: build xo-randomgen + run: | + XONAME=xo-randomgen + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-ordinaltree uses: actions/checkout@v3 with: From 9e19cb85823aafc474afe593c6a3ea3443c12d28 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:32:51 -0400 Subject: [PATCH 0501/2524] worfklow: bugfix: repo name for xo-randomgen --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e0d1e7d4..a5ec0bb5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -314,7 +314,7 @@ jobs: - name: clone xo-randomgen uses: actions/checkout@v3 with: - repository: Rconybea/xo-randomgen + repository: Rconybea/randomgen path: repo/xo-randomgen - name: build xo-randomgen From a70370d239bfb78e930d290cf905eac4c43a768b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:43:42 -0400 Subject: [PATCH 0502/2524] workflows: + eigen3 dep --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a5ec0bb5..f6a824ec 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,6 +29,10 @@ jobs: sudo apt-get install -y catch2 echo "::endgroup" + echo "::group::install eigen3" + sudo apt-get install -y eigen3-dev + echo "::endgroup" + #echo "::group::install pybind11" #sudo apt-get install -y pybind11-dev #echo "::endgroup" From 70f53491381c4dd5736175b104f5d150b25a77db Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 13:45:24 -0400 Subject: [PATCH 0503/2524] workflow: bugfix: eigen3-dev -> libeigen3-dev --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f6a824ec..ee59661d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,7 +30,7 @@ jobs: echo "::endgroup" echo "::group::install eigen3" - sudo apt-get install -y eigen3-dev + sudo apt-get install -y libeigen3-dev echo "::endgroup" #echo "::group::install pybind11" From da011d38cf4713c02a2e6bb9676ed3cd126756fa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 14:18:11 -0400 Subject: [PATCH 0504/2524] workflow: + xo-statistics dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ee59661d..3c4faa53 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -420,6 +420,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-statistics + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-statistics + path: repo/xo-statistics + + - name: build xo-statistics + run: | + XONAME=xo-statistics + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: Configure self (kalmanfilter) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From 99e029c352db26f01edda3930068797dc50b4f56 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 14:33:31 -0400 Subject: [PATCH 0505/2524] worfklows: + ubuntu build on main branch --- .github/workflows/main.yml | 488 +++++++++++++++++++++++++++++++++++++ 1 file changed, 488 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..2f07df30 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,488 @@ +name: build xo-optionutil + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + echo "::group::install catch2" + # install catch2. see + # [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + sudo apt-get install -y catch2 + echo "::endgroup" + + echo "::group::install eigen3" + sudo apt-get install -y libeigen3-dev + echo "::endgroup" + + #echo "::group::install pybind11" + #sudo apt-get install -y pybind11-dev + #echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: build xo-cmake + run: | + XONAME=xo-cmake + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/xo-indentlog + + - name: build xo-indentlog + run: | + XONAME=xo-indentlog + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/xo-refcnt + + - name: build xo-refcnt + run: | + XONAME=xo-refcnt + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/xo-subsys + + - name: build xo-subsys + run: | + XONAME=xo-subsys + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/xo-reflect + + - name: build xo-reflect + run: | + XONAME=xo-reflect + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/xo-callback + + - name: build xo-callback + run: | + XONAME=xo-callback + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/xo-printjson + + - name: build xo-printjson + run: | + XONAME=xo-printjson + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/xo-webutil + + - name: build xo-webutil + run: | + XONAME=xo-webutil + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/xo-randomgen + + - name: build xo-randomgen + run: | + XONAME=xo-randomgen + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/xo-ordinaltree + + - name: build xo-ordinaltree + run: | + XONAME=xo-ordinaltree + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/xo-reactor + + - name: build xo-reactor + run: | + XONAME=xo-reactor + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-statistics + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-statistics + path: repo/xo-statistics + + - name: build xo-statistics + run: | + XONAME=xo-statistics + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: Configure self (pykalmanfilter) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: | + XONAME=xo-pykalmanfilter + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + (cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}}) From 037665298029a42981c211346aa67607f6996d4a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 14:35:44 -0400 Subject: [PATCH 0506/2524] worfklow: + missing pybind11 dep --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2f07df30..81b35479 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,8 +33,8 @@ jobs: sudo apt-get install -y libeigen3-dev echo "::endgroup" - #echo "::group::install pybind11" - #sudo apt-get install -y pybind11-dev + echo "::group::install pybind11" + sudo apt-get install -y pybind11-dev #echo "::endgroup" # ---------------------------------------------------------------- From 17fc09190cf2f91f295efb60363447d4ff6165ad Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 14:38:30 -0400 Subject: [PATCH 0507/2524] workflow: + xo-kalmanfilter dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 81b35479..2a1a335f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -455,6 +455,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-kalmanfilter + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-kalmanfilter + path: repo/xo-kalmanfilter + + - name: build xo-kalmanfilter + run: | + XONAME=xo-kalmanfilter + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: Configure self (pykalmanfilter) # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type From a85a7782a378d62a587270a2d7387b9db6d92d9d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 14:47:17 -0400 Subject: [PATCH 0508/2524] workflow: + xo-pyreactor dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2a1a335f..f82d7615 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -420,6 +420,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-pyreactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyreactor + path: repo/xo-pyreactor + + - name: build xo-pyreactor + run: | + XONAME=xo-pyreactor + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-statistics uses: actions/checkout@v3 with: From 86acc27ce6455673621578609ed26c1e07cb70d0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 14:55:35 -0400 Subject: [PATCH 0509/2524] workflow: + xo-pyprintjson dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f82d7615..d9aa8e13 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -280,6 +280,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-pyprintjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyprintjson + path: repo/xo-pyprintjson + + - name: build xo-pyprintjson + run: | + XONAME=xo-pyprintjson + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-webutil uses: actions/checkout@v3 with: From 1e4d83ae418a67783c552f0b0e8b70ade67f1427 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 14:58:35 -0400 Subject: [PATCH 0510/2524] workflow: + xo-pyreflect dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d9aa8e13..0b6ae004 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -210,6 +210,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-pyreflect + uses: actions/checkout@v3 + with: + repository: Rconybea/pyreflect + path: repo/xo-pyreflect + + - name: build xo-pyreflect + run: | + XONAME=xo-pyreflect + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-callback uses: actions/checkout@v3 with: From ced5e755b5e926b4efe2a27b8f02123af819deaa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 15:02:16 -0400 Subject: [PATCH 0511/2524] workflow: bugfix: rconybea/pyreactor -> rconybea/xo-pyreactor --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0b6ae004..2a06f02d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -213,7 +213,7 @@ jobs: - name: clone xo-pyreflect uses: actions/checkout@v3 with: - repository: Rconybea/pyreflect + repository: Rconybea/xo-pyreflect path: repo/xo-pyreflect - name: build xo-pyreflect From 37649bca068cdb00037506cec6cade6a7aa9470e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 16 Mar 2024 15:05:46 -0400 Subject: [PATCH 0512/2524] workflow: bugfix: + xo-pyutil dep --- .github/workflows/main.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2a06f02d..24a0c9c7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -210,6 +210,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-pyutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-pyutil + path: repo/xo-pyutil + + - name: build xo-pyutil + run: | + XONAME=xo-pyutil + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-pyreflect uses: actions/checkout@v3 with: From ddc15c0416a863658d68e19c881052544f9caff2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0513/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From fdefb286b0b7222687192d505ec50706ffb5582a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0514/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 35ac47555864c3f7051d9870d55f9581b57108a9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0515/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 591c2451d86fe0568e4022da5b3afc72af1e69ef Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0516/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 8c985854977d210cd6c51e675cabe866dd291904 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0517/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From c8947337066160bb5be3544f995e9be6395cf496 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0518/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 06bd4324305996db9b24a98c010a63c4593c1e21 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0519/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 17d26aaf4efa1e772ea7961d09c297f3d4055234 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0520/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From dd2bebc7e871f3112fc5ca0f64068e5095ab424a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0521/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 0bce4cdc91bfd3d6023beb67ea6932252c792839 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0522/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 791583399dc274ba958047ed2c4c4cf7f0b0f8ae Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0523/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 8c196a35a7b2d0d5dcd804167da9f104ba77e448 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0524/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 1051e0e60f13639bd9e5aafcadd05c6f966bef85 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0525/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From a929e8112b1d96d250755ae0fb0b39c32c2f6a47 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0526/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From cc5c53d470f943f3db4935f52a1c2a8e7e3af4d3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0527/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 5db07574c9d4089b4821c1b233f901ed4ad80699 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0528/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 267b8aa78389f4c8f638811f9ce6d46679b30d3e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0529/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 5a45859f5167c625b225774efe81e376558a08de Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0530/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From a647a2535bf57772d2421fbee848a19ca9d24163 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0531/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 9e7aebd6dd8c74c7e637f0645e7067149ccdf2da Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0532/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 4aaf33164f6760c65173405714882237ed5a6ca9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0533/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 2abb2a47f4cfb9ebcb85eed91028a2a2c79765df Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0534/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From 707af0552005a8d98124659a90253cd3325c1707 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:33:41 -0400 Subject: [PATCH 0535/2524] build: suppress bootstrap message in submodule build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From cffffdae7c15273dfad8e04acf64193e644bda81 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:43:12 -0400 Subject: [PATCH 0536/2524] comsetic: whitespace --- include/xo/cxxutil/demangle.hpp | 120 ++++++++++++++++---------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/include/xo/cxxutil/demangle.hpp b/include/xo/cxxutil/demangle.hpp index 8b8b8ba3..e2184f5d 100644 --- a/include/xo/cxxutil/demangle.hpp +++ b/include/xo/cxxutil/demangle.hpp @@ -8,85 +8,85 @@ #include // std::index_sequence namespace xo { - namespace reflect { + namespace reflect { - template - constexpr auto - substring_as_array(std::string_view str, - std::index_sequence indexes) - { - //return std::array{str[Idxs]..., '\n'}; - return std::array{str[Idxs]...}; - } /*substring_as_array*/ + template + constexpr auto + substring_as_array(std::string_view str, + std::index_sequence indexes) + { + //return std::array{str[Idxs]..., '\n'}; + return std::array{str[Idxs]...}; + } /*substring_as_array*/ - template constexpr auto type_name_array() { + template constexpr auto type_name_array() { #if defined(__clang__) - constexpr auto prefix = std::string_view{"[T = "}; - constexpr auto suffix = std::string_view{"]"}; - constexpr auto function = std::string_view{__PRETTY_FUNCTION__}; + constexpr auto prefix = std::string_view{"[T = "}; + constexpr auto suffix = std::string_view{"]"}; + constexpr auto function = std::string_view{__PRETTY_FUNCTION__}; #elif defined(__GNUC__) - constexpr auto prefix = std::string_view{"with T = "}; - constexpr auto suffix = std::string_view{"]"}; - constexpr auto function = std::string_view{__PRETTY_FUNCTION__}; + constexpr auto prefix = std::string_view{"with T = "}; + constexpr auto suffix = std::string_view{"]"}; + constexpr auto function = std::string_view{__PRETTY_FUNCTION__}; #elif defined(_MSC_VER) - constexpr auto prefix = std::string_view{"type_name_array<"}; - constexpr auto suffix = std::string_view{">(void)"}; - constexpr auto function = std::string_view{__FUNCSIG__}; + constexpr auto prefix = std::string_view{"type_name_array<"}; + constexpr auto suffix = std::string_view{">(void)"}; + constexpr auto function = std::string_view{__FUNCSIG__}; #else # error type_name_array: Unsupported compiler #endif - constexpr auto start = function.find(prefix) + prefix.size(); - constexpr auto end = function.rfind(suffix); + constexpr auto start = function.find(prefix) + prefix.size(); + constexpr auto end = function.rfind(suffix); - //static_assert(start < end); + //static_assert(start < end); - constexpr auto name = function.substr(start, (end - start)); + constexpr auto name = function.substr(start, (end - start)); - constexpr auto ixseq = std::make_index_sequence{}; + constexpr auto ixseq = std::make_index_sequence{}; - return substring_as_array(name, ixseq); - } /*type_name_array*/ + return substring_as_array(name, ixseq); + } /*type_name_array*/ - template - struct type_name_holder { - static inline constexpr auto value = type_name_array(); - }; - - template - constexpr auto type_name() -> std::string_view - { - constexpr auto& value = type_name_holder::value; - return std::string_view{value.data(), value.size()}; - } + template + struct type_name_holder { + static inline constexpr auto value = type_name_array(); + }; + + template + constexpr auto type_name() -> std::string_view + { + constexpr auto& value = type_name_holder::value; + return std::string_view{value.data(), value.size()}; + } #ifdef NOT_IN_USE - template - struct join - { - // Join all strings into a single std::array of chars - static constexpr auto impl() noexcept - { - constexpr std::size_t len = (Strs.size() + ... + 0); - std::array arr{}; - auto append = [i = 0, &arr](auto const& s) mutable { - for (auto c : s) arr[i++] = c; + template + struct join + { + // Join all strings into a single std::array of chars + static constexpr auto impl() noexcept + { + constexpr std::size_t len = (Strs.size() + ... + 0); + std::array arr{}; + auto append = [i = 0, &arr](auto const& s) mutable { + for (auto c : s) arr[i++] = c; + }; + (append(Strs), ...); + arr[len] = 0; + return arr; + } + // Give the joined string static storage + static constexpr auto arr = impl(); + // View as a std::string_view + static constexpr std::string_view value {arr.data(), arr.size() - 1}; }; - (append(Strs), ...); - arr[len] = 0; - return arr; - } - // Give the joined string static storage - static constexpr auto arr = impl(); - // View as a std::string_view - static constexpr std::string_view value {arr.data(), arr.size() - 1}; - }; - // Helper to get the value out - template - static constexpr auto join_v = join::value; + // Helper to get the value out + template + static constexpr auto join_v = join::value; #endif - } /*namespace reflect*/ + } /*namespace reflect*/ } /*namespace xo*/ /* end demangle.hpp */ From a449316999d2724f26f5711c5dfd6350c43d221f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:43:50 -0400 Subject: [PATCH 0537/2524] uniformgen: + interval() method --- include/xo/randomgen/uniformgen.hpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/include/xo/randomgen/uniformgen.hpp b/include/xo/randomgen/uniformgen.hpp index d1c4d62b..28e311ca 100644 --- a/include/xo/randomgen/uniformgen.hpp +++ b/include/xo/randomgen/uniformgen.hpp @@ -12,11 +12,19 @@ namespace xo { public: using generator_type = generator>; - template - static generator_type unit(Engine eng) { + /* named ctor idiom */ + template + static generator_type unit(Eng eng) { return make_generator(std::move(eng), std::uniform_real_distribution(0.0, 1.0)); } + + /* named ctor idiom */ + template + static generator_type interval(Eng eng, double lo, double hi) { + return make_generator(std::move(eng), + std::uniform_real_distribution(lo, hi)); + } }; } /*namespace rng*/ } /*namespace xo*/ From be5a3261f5783bf297c912610b0421c36f14d79b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:44:15 -0400 Subject: [PATCH 0538/2524] minor comment --- include/xo/randomgen/random_seed.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/xo/randomgen/random_seed.hpp b/include/xo/randomgen/random_seed.hpp index 92447b3c..d68fc10c 100644 --- a/include/xo/randomgen/random_seed.hpp +++ b/include/xo/randomgen/random_seed.hpp @@ -76,6 +76,7 @@ namespace xo { operator<<(std::ostream & os, Seed const & x) { + /* NOTE: if compile error here, may want caller to #include [indentlog/print/vector.hpp] */ os << x.seed_; return os; } /*operator<<*/ From 2602b751945aa5b77155fff70890c85014c3035b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:47:57 -0400 Subject: [PATCH 0539/2524] minor: unused decl nit --- utest/bplustree.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/bplustree.cpp b/utest/bplustree.cpp index a38fd545..825f33b4 100644 --- a/utest/bplustree.cpp +++ b/utest/bplustree.cpp @@ -20,7 +20,7 @@ namespace { using utest::TreeUtil; using xo::scope; - using xo::scope_setup; + //using xo::scope_setup; using xo::xtag; using BtreeKey = int; From a5f060f3580b47426008a4cc68b6fec3f8376b73 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 29 Mar 2024 14:51:22 -0400 Subject: [PATCH 0540/2524] cosmetic: whitespace --- include/xo/kalmanfilter/KalmanFilterStep.hpp | 202 +++++++++---------- src/kalmanfilter/CMakeLists.txt | 9 +- 2 files changed, 105 insertions(+), 106 deletions(-) diff --git a/include/xo/kalmanfilter/KalmanFilterStep.hpp b/include/xo/kalmanfilter/KalmanFilterStep.hpp index 89813cc2..432ac570 100644 --- a/include/xo/kalmanfilter/KalmanFilterStep.hpp +++ b/include/xo/kalmanfilter/KalmanFilterStep.hpp @@ -8,121 +8,121 @@ #include "KalmanFilterObservable.hpp" namespace xo { - namespace kalman { - /* encapsulate {state + observation} models for a single time step t(k). - * Emitted by KalmanFilterSpec, q.v. - */ - class KalmanFilterStepBase { - public: - KalmanFilterStepBase() = default; - KalmanFilterStepBase(KalmanFilterTransition model, - KalmanFilterObservable obs) - : model_{std::move(model)}, - obs_{std::move(obs)} {} + namespace kalman { + /* encapsulate {state + observation} models for a single time step t(k). + * Emitted by KalmanFilterSpec, q.v. + */ + class KalmanFilterStepBase { + public: + KalmanFilterStepBase() = default; + KalmanFilterStepBase(KalmanFilterTransition model, + KalmanFilterObservable obs) + : model_{std::move(model)}, + obs_{std::move(obs)} {} - /* aka system_model() */ - KalmanFilterTransition const & model() const { return model_; } - KalmanFilterObservable const & obs() const { return obs_; } + /* aka system_model() */ + KalmanFilterTransition const & model() const { return model_; } + KalmanFilterObservable const & obs() const { return obs_; } - private: - /* model for process being observed (state transition + noise) */ - KalmanFilterTransition model_; - /* what can be observed (observables + noise) */ - KalmanFilterObservable obs_; - }; /*KalmanFilterStepBase*/ + private: + /* model for process being observed (state transition + noise) */ + KalmanFilterTransition model_; + /* what can be observed (observables + noise) */ + KalmanFilterObservable obs_; + }; /*KalmanFilterStepBase*/ - /* encapsulate {state + observation} models for a single time step t(k). - * Emitted by KalmanFilterSpec, q.v. - * - * holds: - * x(k) - * P(k) - * F(k) - * H(k+1) - * z(k+1) - * - * contains all the inputs needed to compute: - * x(k+1) - * P(k+1) - * - * does not provide that result - */ - class KalmanFilterStep : public KalmanFilterStepBase { - public: - using utc_nanos = xo::time::utc_nanos; - using MatrixXd = Eigen::MatrixXd; - using VectorXd = Eigen::VectorXd; + /* encapsulate {state + observation} models for a single time step t(k). + * Emitted by KalmanFilterSpec, q.v. + * + * holds: + * x(k) + * P(k) + * F(k) + * H(k+1) + * z(k+1) + * + * contains all the inputs needed to compute: + * x(k+1) + * P(k+1) + * + * does not provide that result + */ + class KalmanFilterStep : public KalmanFilterStepBase { + public: + using utc_nanos = xo::time::utc_nanos; + using MatrixXd = Eigen::MatrixXd; + using VectorXd = Eigen::VectorXd; - public: - KalmanFilterStep() = default; - KalmanFilterStep(ref::rp state, - KalmanFilterTransition model, - KalmanFilterObservable obs, - ref::rp zkp1) - : KalmanFilterStepBase(model, obs), - state_{std::move(state)}, - input_{std::move(zkp1)} {} - - ref::rp const & state() const { return state_; } - ref::rp const & input() const { return input_; } + public: + KalmanFilterStep() = default; + KalmanFilterStep(ref::rp state, + KalmanFilterTransition model, + KalmanFilterObservable obs, + ref::rp zkp1) + : KalmanFilterStepBase(model, obs), + state_{std::move(state)}, + input_{std::move(zkp1)} {} - utc_nanos tkp1() const { return input_->tkp1(); } + ref::rp const & state() const { return state_; } + ref::rp const & input() const { return input_; } - /* extrapolate kalman filter state forward to time - * .tkp1() (i.e. to t(k+1)); computes - * x(k+1|k) - * P(k+1|k) - * does not use the t(k+1) observations .input.z - */ - ref::rp extrapolate() const; + utc_nanos tkp1() const { return input_->tkp1(); } - /* compute kalman gain matrix K(k+1) - * given extrapolated t(k+1) state skp1_ext = {x(k+1|k), P(k+1|k)} - * - * note that .state() != skp1_ext; .state() reports {x(k), P(k)} - */ - MatrixXd gain(ref::rp const & skp1_ext) const; + /* extrapolate kalman filter state forward to time + * .tkp1() (i.e. to t(k+1)); computes + * x(k+1|k) + * P(k+1|k) + * does not use the t(k+1) observations .input.z + */ + ref::rp extrapolate() const; - /* compute kalman gain vector K(k+1) - * given extrapolated t(k+1) state skp1_ext = {x(k+1|k), P(k+1|k)}, - * on behalf of a single observation z[j]. - * actual observation z[j] is not given here, - * just computing the gain vector. i'th member of gain vector - * gives effect of innovation on i'th member of kalman filter state. - */ - VectorXd gain1(ref::rp const & skp1_ext, - uint32_t j) const; + /* compute kalman gain matrix K(k+1) + * given extrapolated t(k+1) state skp1_ext = {x(k+1|k), P(k+1|k)} + * + * note that .state() != skp1_ext; .state() reports {x(k), P(k)} + */ + MatrixXd gain(ref::rp const & skp1_ext) const; - /* compute correction to extrapolated filter state {x(k+1|k), P(k+1|k)}, - * for observation z(k+1) = .input.z() - */ - ref::rp correct(ref::rp const & skp1_ext); + /* compute kalman gain vector K(k+1) + * given extrapolated t(k+1) state skp1_ext = {x(k+1|k), P(k+1|k)}, + * on behalf of a single observation z[j]. + * actual observation z[j] is not given here, + * just computing the gain vector. i'th member of gain vector + * gives effect of innovation on i'th member of kalman filter state. + */ + VectorXd gain1(ref::rp const & skp1_ext, + uint32_t j) const; - /* compute correction to extrapolated filter state skp1_ext = {x(k+1|k), P(k+1|k)}, - * for a single observation z(k+1, j) = .input.z()[j] - */ - ref::rp correct1(ref::rp const & skp1_ext, - uint32_t j); + /* compute correction to extrapolated filter state {x(k+1|k), P(k+1|k)}, + * for observation z(k+1) = .input.z() + */ + ref::rp correct(ref::rp const & skp1_ext); - void display(std::ostream & os) const; - std::string display_string() const; + /* compute correction to extrapolated filter state skp1_ext = {x(k+1|k), P(k+1|k)}, + * for a single observation z(k+1, j) = .input.z()[j] + */ + ref::rp correct1(ref::rp const & skp1_ext, + uint32_t j); - private: - /* system state: timestamp, estimated process state, process covariance - * asof beginning of this step - */ - ref::rp state_; - /* input: observations at time t(k+1) */ - KalmanFilterInputPtr input_; - }; /*KalmanFilterStep*/ + void display(std::ostream & os) const; + std::string display_string() const; - inline std::ostream & - operator<<(std::ostream & os, KalmanFilterStep const & x) { - x.display(os); - return os; - } /*operator<<*/ + private: + /* system state: timestamp, estimated process state, process covariance + * asof beginning of this step + */ + ref::rp state_; + /* input: observations at time t(k+1) */ + KalmanFilterInputPtr input_; + }; /*KalmanFilterStep*/ - } /*namespace kalman*/ + inline std::ostream & + operator<<(std::ostream & os, KalmanFilterStep const & x) { + x.display(os); + return os; + } /*operator<<*/ + + } /*namespace kalman*/ } /*namespace xo*/ /* end KalmanFilterStep.hpp */ diff --git a/src/kalmanfilter/CMakeLists.txt b/src/kalmanfilter/CMakeLists.txt index b7036526..882db194 100644 --- a/src/kalmanfilter/CMakeLists.txt +++ b/src/kalmanfilter/CMakeLists.txt @@ -21,11 +21,10 @@ set(SELF_SRCS xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- -# internal dependencies +# Dependencies +# +# REMINDER: must coordinate with find_dependency() calls in +# [xo-kalmanfilter/cmake/xo_kalmanfilterConfig.cmake.in] xo_dependency(${SELF_LIB} reactor) - -# ---------------------------------------------------------------- -# external dependencies - xo_external_target_dependency(${SELF_LIB} Eigen3 Eigen3::Eigen) From 5688582f4d83587a50066c2769bec77951ca316f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 18:33:40 -0400 Subject: [PATCH 0541/2524] initial commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..d0133c7d --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# xo-unit From 2f911bc10995085baf7993612748c901a5d26107 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 18:52:36 -0400 Subject: [PATCH 0542/2524] xo-unit: adopt dim-checking unit impl from xo-observable --- include/xo/unit/dim_util.hpp | 59 ++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 include/xo/unit/dim_util.hpp diff --git a/include/xo/unit/dim_util.hpp b/include/xo/unit/dim_util.hpp new file mode 100644 index 00000000..222f9c9e --- /dev/null +++ b/include/xo/unit/dim_util.hpp @@ -0,0 +1,59 @@ +/* @file dim_util.hpp */ + +#pragma once + +#include "stringliteral.hpp" + +namespace xo { + namespace obs { + enum class dim { + /** weight. native unit = 1 gram **/ + mass, + /** distance. native unit = 1 meter **/ + distance, + /** time. native unit = 1 second **/ + time, + /** a currency amount. native unit depends on actual currency. + * For USD: one US dollar. + * + * NOTE: unit system isn't suitable for multicurrency work: + * (1usd + 1eur) is well-defined, but (1sec + 1m) is not. + **/ + currency, + /** a screen price. dimensionless **/ + price, + }; + + enum class native_unit_id { + gram, + meter, + second, + currency, + price + }; + + template + struct native_unit_for; + + template <> + struct native_unit_for { static constexpr auto value = native_unit_id::gram; }; + + template <> + struct native_unit_for { static constexpr auto value = native_unit_id::meter; }; + + template <> + struct native_unit_for { static constexpr auto value = native_unit_id::second; }; + + template <> + struct native_unit_for { static constexpr auto value = native_unit_id::currency; }; + + template <> + struct native_unit_for { static constexpr auto value = native_unit_id::price; }; + + template + constexpr auto native_unit_for_v = native_unit_for::value; + } /*namespace obs*/ +} /*namespace xo*/ + + +/* end dim_util.hpp */ From 3c1f8389c73c88dd9806e8dff51add0cbde2ef42 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 19:34:04 -0400 Subject: [PATCH 0543/2524] build: move unit+quantity feature from xo-observable -> xo-unit --- CMakeLists.txt | 56 + cmake/xo-bootstrap-macros.cmake | 14 + cmake/xo_unitConfig.cmake.in | 17 + docs/CMakeLists.txt | 60 + docs/Doxyfile.in | 2814 ++++++++++++++++++++++++ include/xo/unit/basis_unit.hpp | 86 + include/xo/unit/dimension_concept.hpp | 89 + include/xo/unit/dimension_impl.hpp | 516 +++++ include/xo/unit/native_bpu.hpp | 259 +++ include/xo/unit/native_bpu_concept.hpp | 42 + include/xo/unit/quantity.hpp | 501 +++++ include/xo/unit/quantity_concept.hpp | 21 + include/xo/unit/ratio_concept.hpp | 13 + include/xo/unit/ratio_util.hpp | 242 ++ include/xo/unit/stringliteral.hpp | 170 ++ include/xo/unit/unit.hpp | 394 ++++ utest/CMakeLists.txt | 25 + utest/quantity.test.cpp | 653 ++++++ utest/unit.test.cpp | 346 +++ utest/unit_utest_main.cpp | 6 + 20 files changed, 6324 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_unitConfig.cmake.in create mode 100644 docs/CMakeLists.txt create mode 100644 docs/Doxyfile.in create mode 100644 include/xo/unit/basis_unit.hpp create mode 100644 include/xo/unit/dimension_concept.hpp create mode 100644 include/xo/unit/dimension_impl.hpp create mode 100644 include/xo/unit/native_bpu.hpp create mode 100644 include/xo/unit/native_bpu_concept.hpp create mode 100644 include/xo/unit/quantity.hpp create mode 100644 include/xo/unit/quantity_concept.hpp create mode 100644 include/xo/unit/ratio_concept.hpp create mode 100644 include/xo/unit/ratio_util.hpp create mode 100644 include/xo/unit/stringliteral.hpp create mode 100644 include/xo/unit/unit.hpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/quantity.test.cpp create mode 100644 utest/unit.test.cpp create mode 100644 utest/unit_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..a879e31f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,56 @@ +# xo-unit/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_unit VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(cmake/xo-bootstrap-macros.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 + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +#add_subdirectory(src/unit) +add_subdirectory(utest) +add_subdirectory(docs) + +# ---------------------------------------------------------------- +# provide find_package() support for reactor customers + +set(SELF_LIB xo_unit) +xo_add_headeronly_library(${SELF_LIB}) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# dependencies + +#xo_headeronly_dependency(${SELF_LIB} randomgen) +# etc.. + +# end CMakeLists.txt diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..96592216 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,14 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/cmake/xo_unitConfig.cmake.in b/cmake/xo_unitConfig.cmake.in new file mode 100644 index 00000000..e5ee1778 --- /dev/null +++ b/cmake/xo_unitConfig.cmake.in @@ -0,0 +1,17 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +#find_dependency(reflect) +#find_dependency(subsys) +#find_dependency(Eigen3) +#find_dependency(webutil) +#find_dependency(printjson) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 00000000..99a1fdcc --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,60 @@ +# xo-unit/docs/CMakeLists.txt + +# copied from xo-observable/docs/CMakeLists.txt + +set(DOX_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + +#set(DOX_DEPS ${PROJECT_SOURCE_DIR}/mainpage.dox) # not yet +set(DOX_INPUT_DIR ${PROJECT_SOURCE_DIR}) +set(DOX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dox) + +set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html) + +#set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/sphinx/html) +#set(SPHINX_INDEX_FILE ${SPHINX_OUTPUT_DIR}/index.html) +# +## sphinx .rst files reachable from cmake-examples/docs +#file(GLOB_RECURSE SPHINX_RST_FILES ${CMAKE_CURRENT_SOURCE_DIR} *.rst) + +set(ALL_LIBRARY_TARGETS xo_unit) # todo: automate this from xo-cmake macros +#set(ALL_UTEST_TARGETS "") # todo: automate this from xo-cmake macros + +# look for doxygen executable +find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED) +message("-- DOXYGEN_EXECUTABLE=${DOXYGEN_EXECUTABLE}") + +if (XO_SUBMODULE_BUILD) + # in submodule build, rely on toplevel docs/CMakeLists.txt file instead +else() + # build docs starting from here only in standalone build. + # otherwise use top-level doxygen setup instead. + + # TODO: + # 1. move Doxyfile.in to xo-cmake project + # 2. replace this command section with xo-cmake macro + # + configure_file( + Doxyfile.in ${DOX_CONFIG_FILE} + FILE_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE + @ONLY) + + file(MAKE_DIRECTORY ${DOX_OUTPUT_DIR}) + add_custom_command( + OUTPUT ${DOX_INDEX_FILE} + DEPENDS ${DOX_DEPS} ${ALL_LIBRARY_TARGETS} ${ALL_UTEST_TARGETS} + COMMAND "${DOXYGEN_EXECUTABLE}" ${DOX_CONFIG_FILE} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + MAIN_DEPENDENCY ${DOX_CONFIG_FILE} + COMMENT "Generating docs (doxygen)") + + # To build this target + # $ cmake --build .build -j -- doxygen + # or + # $ cd .build + # $ make doxygen + # + add_custom_target( + doxygen + DEPENDS ${DOX_INDEX_FILE} + ) +endif() diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in new file mode 100644 index 00000000..67b45f8c --- /dev/null +++ b/docs/Doxyfile.in @@ -0,0 +1,2814 @@ +# If filename is Doxyfile.in: +# template (to be expanded by cmake) for real doxyfile +# @SOMEVAR@ expands to value of cmake variable SOMEVAR +# +# expressions to be expanded include: +# @DOX_INPUT_DIR@ +# @DOX_OUTPUT_DIR@ +# +# if filename is Doxyfile: +# expanded template in build directory, to configure doxygen + +# Doxyfile 1.9.7 +# + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Cmake Examples" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @DOX_OUTPUT_DIR@ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = @DOX_INPUT_DIR@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN Use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0. and GITHUB Use the lower case version of title +# with any whitespace replaced by '-' and punctations characters removed.. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = YES + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = YES + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = YES + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = SYSTEM + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @DOX_INPUT_DIR@ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = */utest/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /

); + static_assert(bpu_list_concept); + + /** For example: + * using b1 = basis_power_unit>; + * using b2 = basis_power_unit>; + * using foo = dimension_impl>; + * + * then + * foo::lookup_bpu<0> -> b1 + * foo::lookup_bpu<1> -> b2 + * foo::lookup_bpu<2> -> not defined + **/ + using front_type = P; + using rest_type = D; + + static constexpr std::uint32_t n_dimension = rest_type::n_dimension + 1; + }; + + /** @class dimension + + @brief represent a composite dimension + **/ + template + struct bpu_node { + static_assert(native_bpu_concept); + static_assert(bpu_list_concept); + + using front_type = P0; + using rest_type = void; + + /** For example: + * using b1 = basis_power_unit>; + * using foo = dimension_impl; + * then + * foo::lookup_bpu<0> --> b1 + * foo::lookup_bpu<1> --> not defined + **/ + + /** number of dimensions represented by this struct **/ + static constexpr std::uint32_t n_dimension = 1; + }; + + // ----- bpu_cartesian_product ----- + + /** Require: + * - B isa native_bpu type + * - DI_Front is a native_bpu type + * - DI_Rest is a dimension_impl type + * + * Promise: + * - type isa dimension_impl type + **/ + template < typename B, + typename DI_Front, + typename DI_Rest, + bool MatchesFront = (B::c_native_dim == DI_Front::c_native_dim) > + struct bpu_cartesian_product_helper; + + /** require: + * - B isa native_bpu type + * - DI isa (bpu_list | void) type + **/ + template < typename B, typename DI > + struct bpu_cartesian_product { + static_assert(native_bpu_concept); + static_assert(bpu_list_concept); + + using _tmp = bpu_cartesian_product_helper; + + using outer_scalefactor_type = typename _tmp::outer_scalefactor_type; + static constexpr double c_outer_scalefactor_inexact = _tmp::c_outer_scalefactor_inexact; + + using bpu_list_type = typename _tmp::bpu_list_type; + + static_assert(ratio_concept); + static_assert(bpu_list_concept); + }; + + /** Reminder: void represents the 'no dimension' of a dimensionless quantity **/ + template < typename B > + struct bpu_cartesian_product { + using outer_scalefactor_type = std::ratio<1>; + static constexpr double c_outer_scalefactor_inexact = 1.0; + + using bpu_list_type = bpu_node; + + static_assert(ratio_concept); + static_assert(bpu_list_concept); + }; + + /* specialize for matching front */ + template + struct bpu_cartesian_product_helper { + static_assert(native_bpu_concept); + static_assert(native_bpu_concept); + static_assert(bpu_list_concept); + + /* _mult_type may have zero exponent (power_type); + * in that case bpu_smart_cons will collapse to DI_Rest + */ + using _front_mult_type = bpu_product; + + using _front_type = typename _front_mult_type::native_bpu_type; + using _rest_type = DI_Rest; + + using outer_scalefactor_type = typename _front_mult_type::outer_scalefactor_type; + static constexpr double c_outer_scalefactor_inexact = _front_mult_type::c_outer_scalefactor_inexact; + + using bpu_list_type = bpu_smart_cons_t<_front_type, DI_Rest>; + + static_assert(ratio_concept); + static_assert(bpu_list_concept); + }; + + /* specialize for not-matching-front */ + template + struct bpu_cartesian_product_helper { + static_assert(native_bpu_concept); + static_assert(native_bpu_concept); + static_assert(bpu_list_concept); + + using _rest_mult_type = bpu_cartesian_product< B, DI_Rest >; + + using _front_type = DI_Front; + using _rest_type = typename _rest_mult_type::bpu_list_type; + + using outer_scalefactor_type = typename _rest_mult_type::outer_scalefactor_type; + static constexpr double c_outer_scalefactor_inexact = _rest_mult_type::c_outer_scalefactor_inexact; + + using bpu_list_type = bpu_node; + + static_assert(ratio_concept); + static_assert(bpu_list_concept); + }; + + // ----- di_cartesian_product ----- + + template < typename D1, typename D2 > struct di_cartesian_product; + + // ----- bpu_cartesian_product1 ----- + + template < typename B1, typename R1, typename D2 > + struct di_cartesian_product1 { + static_assert(native_bpu_concept); + static_assert(bpu_list_concept); + static_assert(bpu_list_concept); + + using _tmp1_mult_type = bpu_cartesian_product; + using _tmp1_scalefactor_type = _tmp1_mult_type::outer_scalefactor_type; + using _tmp1_bpu_list_type = _tmp1_mult_type::bpu_list_type; + + using _tmp2_mult_type = di_cartesian_product; + using _tmp2_scalefactor_type = _tmp2_mult_type::outer_scalefactor_type; + using _tmp2_bpu_list_type = _tmp2_mult_type::bpu_list_type; + + using outer_scalefactor_type = std::ratio_multiply< + _tmp1_scalefactor_type, + _tmp2_scalefactor_type>; + static constexpr double c_outer_scalefactor_inexact = (_tmp1_mult_type::c_outer_scalefactor_inexact + * _tmp2_mult_type::c_outer_scalefactor_inexact); + + using bpu_list_type = _tmp2_bpu_list_type; + + static_assert(ratio_concept); + static_assert(bpu_list_concept); + }; + + template < typename B1, typename D2 > + struct di_cartesian_product1 { + static_assert(native_bpu_concept); + static_assert(bpu_list_concept); + + using _tmp_mult_type = bpu_cartesian_product; + + using outer_scalefactor_type = _tmp_mult_type::outer_scalefactor_type; + static constexpr double c_outer_scalefactor_inexact = _tmp_mult_type::c_outer_scalefactor_inexact; + + using bpu_list_type = _tmp_mult_type::bpu_list_type; + }; + + // ----- di_invert ----- + + /* note: rescaling never required here, + * since not combining basis dimensions. + */ + template + struct di_invert; + + template <> + struct di_invert { + using type = void; + }; + + template + struct di_invert { + using type = bpu_node< + typename bpu_invert::type, + typename di_invert::type + >; + }; + + // ----- di_cartesian_product ----- + + template < typename D1, typename D2 > + struct di_cartesian_product { + static_assert(bpu_list_concept); + static_assert(bpu_list_concept); + + using _mult_type = di_cartesian_product1< + typename D1::front_type, + typename D1::rest_type, + D2>; + + using outer_scalefactor_type = _mult_type::outer_scalefactor_type; + static constexpr double c_outer_scalefactor_inexact = _mult_type::c_outer_scalefactor_inexact; + + using bpu_list_type = _mult_type::bpu_list_type; + + static_assert(ratio_concept); + static_assert(bpu_list_concept); + }; + + template < typename D2 > + struct di_cartesian_product< void, D2 > { + static_assert(bpu_list_concept); + + using outer_scalefactor_type = std::ratio<1>; + static constexpr double c_outer_scalefactor_inexact = 1.0; + using bpu_list_type = D2; + }; + + template + struct di_cartesian_product< D1, void > { + static_assert(bpu_list_concept); + + using outer_scalefactor_type = std::ratio<1>; + static constexpr double c_outer_scalefactor_inexact = 1.0; + using bpu_list_type = D1; + }; + + // ----- di_assemble_abbrev ----- + + /* reminder: can't partially specialize a template function -> need struct wrapper */ + template < typename DI > + struct di_assemble_abbrev; + + /** Expect: + * - P isa native_bpu type + * - P::power_type = std::ratio<..> + * - P::c_native_dim :: dim + * - P::c_num :: int + * - P::c_den :: int + * - D isa dimension_impl type + * - D::front_type = native_bpu<..> + * - D::rest_type = dimension_impl<..> + * - D::n_dimension :: int + **/ + template + struct di_assemble_abbrev_helper { + static_assert(native_bpu_concept

); + static_assert(bpu_list_concept); + + static constexpr auto _prefix = bpu_assemble_abbrev

(); + static constexpr auto _suffix = di_assemble_abbrev::value; + + static constexpr auto value = stringliteral_concat(_prefix.value_, + ".", + _suffix.value_); + }; + + template + struct di_assemble_abbrev_helper { + static constexpr auto value = bpu_assemble_abbrev

(); + }; + + template < typename DI > + struct di_assemble_abbrev { + static_assert(bpu_list_concept); + + using _helper_type = di_assemble_abbrev_helper ; + + static constexpr auto value = _helper_type::value; + }; + + template <> + struct di_assemble_abbrev { + static constexpr auto value = stringliteral(""); + }; + + // ----- canonical_impl ----- + + template + struct canonical_impl { + /* + * bwp_front::c_index + * bwp_front::c_native_dim + */ + using _bwp_front = native_lo_bwp_of::bwp_type; + + using _front_type = typename lookup_bpu::power_unit_type; + using _rest0_type = typename without_elt::dim_type; + using _rest_type = canonical_impl<_rest0_type>::dim_type; + + using dim_type = bpu_node<_front_type, _rest_type>; + }; + + /** compute canonical renumbering of a dimension + **/ + template <> + struct canonical_impl { + using dim_type = void; + }; + + template + using canonical_t = canonical_impl::dim_type; + + } /*namespace obs*/ +} /*namespace xo*/ + +/* end dimension_impl.hpp */ diff --git a/include/xo/unit/native_bpu.hpp b/include/xo/unit/native_bpu.hpp new file mode 100644 index 00000000..a3e716a0 --- /dev/null +++ b/include/xo/unit/native_bpu.hpp @@ -0,0 +1,259 @@ +/* @file native_bpu.hpp */ + +#pragma once + +#include "native_bpu_concept.hpp" +#include "basis_unit.hpp" +#include + +namespace xo { + namespace obs { + // ----- native_bpu ----- + + /** @class native_bpu + + @brief represent product of a compile-time scale-factor with a rational power of a native unit + + Example: + native_bpu, ratio<-1,2>> represents unit of 1/sqrt(t) + **/ + template< + dim DimId, + typename InnerScale, + typename Power = std::ratio<1> > + struct bpu : basis_unit, InnerScale> { + static_assert(ratio_concept); + + /* native_unit provides + * - scalefactor_type --> std::ratio + * - c_native_dim :: dim + * - c_native_unit :: native_unit + */ + + using power_type = Power; + + static const int c_num = Power::num; + static const int c_den = Power::den; + }; + + /** @class bpu_assemble_abbrev + * + * @brief generate abbreviation literal. + * + * Abbreviation literal ignores outer scale factor; + * (outer scale factor should be multiplied by run-time scale when printing a quantity) + * + * Separate template from native_bpu so that abbrev can independently be specialized + **/ + template < dim dim_id, + typename InnerScale, + typename Power = std::ratio<1> > + constexpr auto bpu_assemble_abbrev_helper() { + static_assert(ratio_concept); + + return stringliteral_concat(units::scaled_native_unit_abbrev_v.value_, + stringliteral_from_exponent().value_); + }; + + /** Expect: + * - BPU is a native_bpu type: + * - BPU::scalefactor_type = std::ratio<..> + * - BPU::c_native_dim :: dim + * - BPU::power_type = std::ratio<..> + * - BPU::c_num :: int + * - BPU::c_den :: int + **/ + template < typename BPU > + constexpr auto bpu_assemble_abbrev() { + static_assert(native_bpu_concept); + + return bpu_assemble_abbrev_helper< BPU::c_native_dim, + typename BPU::scalefactor_type, + typename BPU::power_type >(); + }; + + // ----- bpu_rescale ----- + + /** + * Part I + * ------ + * We have B satisfying native_bpu_concept: + * B represents a basis-power-unit + * p + * (b.u) + * + * with + * b = B::scalefactor_type, e.g. 60 for a 1-minute unit + * u = B::dim, e.g. 1second for time + * p = B::power_type + * + * We want to construct something with similar form: + * + * p + * a'.(b'.u) + * + * representing the same dimensioned unit, + * i.e. + * p p' + * (b.u) = a'.(b'.u) + * + * with NewInnerScale -> b' + * + * p p p p + * (b.u) = (b/b') . (b'.u) = a'.(b'.u) + * + * p + * with a' = (b/b') + * + * For example: if we have B(b=60,u=time,p=2), NewInnerScale=1: + * then we want a'=3600, B'(b=1,u=time,p=2) + * + * Result represented with + * bpu_rescale::outer_scalefactor_type -> 'a + * bpu_rescale::native_bpu_type -> B' + * + * Part II + * ------- + * Want ability to rescale when p is a non-integer rational. + * In that case a' = (b/b')^p won't in general be exactly-representable, + * so we are forced to accept some loss of precision. + * + * Want to write: + * p as p' + q' with: + * p' = integer part of p + * q' = fractional part of p + * Then we can write + * a' as c'.d' with: + * c' = (b/b')^p' [exactly represented] + * d' = (b/b')^q' [floating point] + **/ + template + struct bpu_rescale { + static_assert(native_bpu_concept); + static_assert(ratio_concept); + + /* TODO: + * - native_unit::c_scale -> std::ratio, call it c_inner_scalefactor + * - ++ native_bpu::c_outer_scalefactor, will be a std::ratio + */ + + /* b/b' */ + using _t1_type = std::ratio_divide + < typename B::scalefactor_type, NewInnerScale >; + + /* p' */ + using p1_type = ratio_floor_t; + /* q' */ + using q1_type = ratio_frac_t; + + /** require p must be integral **/ + static_assert(p1_type::den == 1); + + /* note: constexpr from c++26, but already present in earlier gcc */ + static constexpr double c_outer_scalefactor_inexact = ::pow(from_ratio(), + from_ratio()); + + /** p + * a' = (b/b') + **/ + using outer_scalefactor_type = ratio_power_t< _t1_type, p1_type::num >; + + /** + * p + * (b'.u) + **/ + using native_bpu_type = bpu < B::c_native_dim, + NewInnerScale, + typename B::power_type >; + }; + + // ----- bpu_invert ----- + + /** invert a native bpu: create type for space 1/B **/ + template + struct bpu_invert { + using type = bpu < + B::c_native_dim, + typename B::scalefactor_type, + std::ratio_multiply, typename B::power_type> + >; + }; + + // ----- bpu_product ----- + + /** Suppose we have two native_bpu's {B1, B2} that scale the same native basis unit u. + * B1,B2 may be using different units {b1,b2} for u + * + * p1 + * B1 (b1, u, p1) = (b1.u) + * + * p2 + * B2 (b2, u, p2) = (b2.u) + * + * we want a representation in similar form: + * + * p' + * a' . B' (b', u, p') = a'.(b'.u) + * + * for the product (B1 x B2), i.e. + * + * p' p1 p2 + * a'.(b'.u) = (b1.u) (b2.u) + * + * We can use bpu_rescale to rewrite B2 in the form + * + * p2 + * B2' = (c'.d').(b1.u) + * + * where c' is exact, d' is inexact. + * (note however d' will be exactly 1.0 whenever p2 is integral) + * + * so we have + * + * p1 p2 + * (B1 x B2) = (b1.u) (c'.d').(b1.u) + * + * p1+p2 + * = (c'.d').(b1.u) + * + **/ + template < typename B1, typename B2 > + struct bpu_product { + static_assert(native_bpu_concept); + static_assert(native_bpu_concept); + static_assert(B1::c_native_dim == B2::c_native_dim); + + /* c'.d'.B2' = c'.d'.(b1.u)^p2 + * + * _b2p_rescaled_type::native_bpu_type -> B2' (b1, u, p2) [same basis scalefactor as B1] + * _b2p_rescaled_type::outer_scalefactor_type -> c' [exact factor] + * _b2p_rescaled_type::c_outer_scalefactor_type -> d' [inexact factor, from fractional powers] + */ + using _b2p_rescaled_type = bpu_rescale; + /* (b1.u)^p2 */ + using _b2p_sf_bpu_type = _b2p_rescaled_type::native_bpu_type; + + /* p1+p2 */ + using _p_type = std::ratio_add< + typename B1::power_type, + typename B2::power_type + >; + + /* c' */ + using outer_scalefactor_type = _b2p_rescaled_type::outer_scalefactor_type; + /* d' */ + static constexpr double c_outer_scalefactor_inexact = _b2p_rescaled_type::c_outer_scalefactor_inexact; + + /* (b1.u)^(p1+p2) */ + using native_bpu_type = bpu < + B1::c_native_dim, + typename B1::scalefactor_type, + _p_type /*Power*/ >; + }; + + } /*namespace obs*/ +} /*namespace xo*/ + +/* end native_bpu.hpp */ diff --git a/include/xo/unit/native_bpu_concept.hpp b/include/xo/unit/native_bpu_concept.hpp new file mode 100644 index 00000000..776e18bf --- /dev/null +++ b/include/xo/unit/native_bpu_concept.hpp @@ -0,0 +1,42 @@ +/* @file native_bpu_concept.hpp */ + +#pragma once + +#include "ratio_concept.hpp" +#include "dim_util.hpp" +#include + +namespace xo { + namespace obs { + /** + * e.g. see native_bpu> + * + * bpu short for 'basis power unit'. + * + * NOTE: in typical c++ use, there won't be a reason to declare + * a variable of type NativeBpu. Instead will appear + * as a template argument. + **/ + template + concept native_bpu_concept = requires(NativeBpu bpu) + { + typename NativeBpu::scalefactor_type; + typename NativeBpu::power_type; + + // NativeBpu::c_native_dim :: native_dim_id + // NativeBpu::c_scale :: std::intmax_t + // NativeBpu::num :: int + // NativeBpu::den :: int + } + && ((std::is_same_v) + && ratio_concept + && ratio_concept + && (std::is_signed_v) + && (std::is_signed_v)) + // && std::copyable + ; + } /*namespace obs*/ +} /*namespace xo*/ + + +/* end native_bpu_concept.hpp */ diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp new file mode 100644 index 00000000..286a9e16 --- /dev/null +++ b/include/xo/unit/quantity.hpp @@ -0,0 +1,501 @@ +/* @file quantity.hpp */ + +#pragma once + +#include "quantity_concept.hpp" +#include "unit.hpp" +//#include "xo/reflect/Reflect.hpp" +//#include "xo/indentlog/scope.hpp" + +namespace xo { + namespace obs { + /** @class promoter + * + * Aux class assister for quantity::promote() + **/ + template > + struct promoter; + + // ----- quantity ----- + + /** @class quantity + * + * @brief represets a scalar quantity; enforces dimensional consistency at compile time + * + * Repr representation. + * Unit unit + * Assert use to specify required unit dimension + * + * Require: + * - Repr copyable, assignable + * - Repr = 0 + * - Repr = 1 + * - Repr + Repr -> Repr + * - Repr - Repr -> Repr + * - Repr * Repr -> Repr + * - Repr / Repr -> Repr + **/ + template //, typename RequiredDimensionType = Unit> + class quantity { + public: + using unit_type = Unit; + using repr_type = Repr; + + /* non-unity compile-time scale factors can arise during unit conversion; + * for example see method quantity::in_units_of() + */ + static_assert(std::same_as< typename Unit::scalefactor_type, std::ratio<1> >); + static_assert(std::same_as< typename Unit::canon_type, typename Unit::canon_type >); + + public: + constexpr quantity() = default; + constexpr quantity(quantity const & x) = default; + constexpr quantity(quantity && x) = default; + + template + using find_bpu_t = unit_find_bpu_t; + + /** + * For example: + * auto q = qty::milliseconds(5) * qty::seconds(1); + * then + * q.basis_power -> 2 + * q.basis_power -> 0 + **/ + template + static constexpr PowerRepr c_basis_power = from_ratio::power_type>(); + + /** @brief get scale value (relative to unit) (@ref scale_) **/ + constexpr Repr scale() const { return scale_; } + /** @brief abbreviation for this quantity's units **/ + static constexpr char const * unit_cstr() { return unit_abbrev_v.c_str(); } + /** @brief return unit quantity -- amount with this Unit that has representation = 1 **/ + static constexpr quantity unit_quantity() { return quantity(1); } + /** @brief promote representation to quantity. Same as multiplying by Unit **/ + static constexpr auto promote(Repr x) { + //std::cerr << "quantity::promote: x=" << x << ", R=" << reflect::Reflect::require()->canonical_name() << std::endl; + return promoter::promote(x); + } + + template + auto multiply(Quantity2 y) const { + //constexpr bool c_debug_flag = false; + //using Reflect = xo::reflect::Reflect; + + //scope log(XO_DEBUG(c_debug_flag)); + + static_assert(quantity_concept); + + /* unit: may have non-unit scalefactor_type */ + using unit_product_type = unit_cartesian_product; + using exact_unit_type = unit_product_type::exact_unit_type; + using norm_unit_type = normalize_unit_t; + + using exact_scalefactor_type = exact_unit_type::scalefactor_type; + constexpr double c_scalefactor_inexact = unit_product_type::c_scalefactor_inexact; + + using repr_type = std::common_type_t; + + repr_type r_scale = ((scale() * y.scale() * c_scalefactor_inexact * exact_scalefactor_type::num) + / exact_scalefactor_type::den); + +# ifdef NOT_USING_DEBUG + log && log(xtag("unit_product_type", Reflect::require()->canonical_name())); + log && log(xtag("exact_unit_type", Reflect::require()->canonical_name())); + log && log(xtag("norm_unit_type", Reflect::require()->canonical_name())); + log && log(xtag("exact_scalefactor_type", Reflect::require()->canonical_name())); + log && log(xtag("c_scalefactor_inexact", c_scalefactor_inexact)); + log && log(xtag("repr_type", Reflect::require()->canonical_name())); + log && log(xtag("repr_type", Reflect::require()->canonical_name())); +# endif + + return quantity::promote(r_scale); + } + + template + auto divide(Quantity2 y) const { + using unit_divide_type = unit_divide; + using exact_unit_type = unit_divide_type::exact_unit_type; + using norm_unit_type = normalize_unit_t; + + using exact_scalefactor_type = exact_unit_type::scalefactor_type; + constexpr double c_scalefactor_inexact = unit_divide_type::c_scalefactor_inexact; + + using repr_type = std::common_type_t; + + repr_type r_scale = ((scale() * c_scalefactor_inexact * unit_type::scalefactor_type::num) + / (y.scale() * unit_type::scalefactor_type::den)); + + return quantity::promote(r_scale); + } + + // quantity operator*=() + // quantity operator/=() + + /** + * scale by dimensionless number + **/ + template + auto scale_by(Repr2 x) const { + static_assert(!quantity_concept); + + using r_repr_type = std::common_type_t; + + r_repr_type r_scale = this->scale_ * x; + + //std::cerr << "quantity::scale_by: scale=" << scale << ", repr_type=" << reflect::Reflect::require()->canonical_name() << std::endl; + + return quantity::promote(r_scale); + } + + /** + * divide by dimensionless number + **/ + template + auto divide_by(Repr2 x) const { + using r_repr_type = std::common_type_t; + + r_repr_type r_scale = this->scale_ / x; + + return quantity::promote(r_scale); + } + + /** + * divide dimensionless number by this quantity + **/ + template + auto divide_into(Repr2 x) const { + using r_unit_type = unit_invert_t; + using r_repr_type = std::common_type_t; + + r_repr_type r_scale = ((x * r_unit_type::scalefactor_type::num) + / (this->scale_ * r_unit_type::scalefactor_type::den)); + + return quantity::promote(r_scale); + } + + template + Repr2 in_units_of() const { + // static_assert(dimension_of == dimension_of); // discard all the scaling values + + static_assert(same_dimension_v); + + using _convert_to_u2_type = unit_cartesian_product>; + + using exact_scalefactor_type = _convert_to_u2_type::exact_unit_type::scalefactor_type; + constexpr double c_scalefactor_inexact = _convert_to_u2_type::c_scalefactor_inexact; + + // _convert_u2_type + // - scalefactor_type + // - dim_type + // - canon_type + + /* if _convert_u2_type isn't dimensionless, then {Unit2, Unit} have different dimensions */ + + return ((this->scale_ * c_scalefactor_inexact * exact_scalefactor_type::num) / exact_scalefactor_type::den); + } + + template + quantity operator+=(Quantity2 y) { + static_assert(std::same_as< + typename unit_type::canon_type, + typename Quantity2::unit_type::canon_type >); + + /* relying on assignment that correctly converts-to-lhs-units */ + quantity y2 = y; + + this->scale_ += y2.scale(); + + return *this; + } + + template + quantity operator-=(Quantity2 y) { + static_assert(std::same_as< + typename unit_type::canon_type, + typename Quantity2::unit_type::canon_type >); + + quantity y2 = y; + + /* relying on assignment that correctly converts-to-lhs-units */ + this->scale_ -= y2.scale(); + + return *this; + } + + /* convert to quantity with same dimension, different {unit_type, repr_type} */ + template + operator Quantity2 () const { + /* avoid truncating precision when converting: + * use best available representation + */ + using tmp_repr_type = std::common_type_t; + + return Quantity2::promote(this->in_units_of()); + } + + void display(std::ostream & os) const { + os << this->scale() << unit_cstr(); + } + + quantity & operator=(quantity const & x) = default; + quantity & operator=(quantity && x) = default; + + private: + explicit constexpr quantity(Repr x) : scale_{x} {} + + friend class promoter; + friend class promoter; + + private: + /** @brief quantity represents this multiple of a unit (that has compile-time outer-scalefactor of 1) **/ + Repr scale_ = 0; + }; /*quantity*/ + + // ----- promoter ----- + + /* collapse dimensionless quantity to its repr_type> */ + template + struct promoter { + static constexpr Repr promote(Repr x) { return x; }; + }; + + template + struct promoter { + static constexpr quantity promote(Repr x) { return quantity(x); } + }; + + // ----- operator+ ----- + + template + inline Quantity1 operator+ (Quantity1 x, Quantity2 y) { + static_assert(same_dimension_v); + + /* convert y to match units used by x; + * would fail at compile time if this isn't well-defined + */ + Quantity1 y2 = y; + + return Quantity1::promote(x.scale() + y2.scale()); + } + + template + inline Quantity1 operator- (Quantity1 x, Quantity2 y) { + static_assert(std::same_as + < typename Quantity1::unit_type::dimension_type::canon_type, + typename Quantity2::unit_type::dimension_type::canon_type >); + + /* convert y to match units used by x */ + Quantity1 y2 = y; + + return Quantity1::promote(x.scale() - y2.scale()); + } + + template + inline Quantity operator- (Quantity x) { + return Quantity::promote(- x.scale()); + } + + template + inline auto operator* (Quantity1 x, Quantity2 y) { + static_assert(quantity_concept); + static_assert(quantity_concept); + + return x.multiply(y); + } + + /** e.g. DECLARE_LH_MULT(int32_t) **/ +# define DECLARE_LH_MULT(lhtype) \ + template \ + inline auto \ + operator* (lhtype x, Quantity y) { \ + static_assert(quantity_concept); \ + return y.scale_by(x); \ + } + + DECLARE_LH_MULT(int8_t); + DECLARE_LH_MULT(uint8_t); + DECLARE_LH_MULT(int16_t); + DECLARE_LH_MULT(uint16_t); + DECLARE_LH_MULT(int32_t); + DECLARE_LH_MULT(uint32_t); + DECLARE_LH_MULT(int64_t); + DECLARE_LH_MULT(uint64_t); + DECLARE_LH_MULT(float); + DECLARE_LH_MULT(double); +# undef DECLARE_LH_MULT + + /** e.g. DECLARE_RH_MULT(int32_t) **/ +# define DECLARE_RH_MULT(rhtype) \ + template \ + inline auto \ + operator* (Quantity x, rhtype y) { \ + static_assert(quantity_concept); \ + return x.scale_by(y); \ + } + + DECLARE_RH_MULT(int8_t); + DECLARE_RH_MULT(uint8_t); + DECLARE_RH_MULT(int16_t); + DECLARE_RH_MULT(uint16_t); + DECLARE_RH_MULT(int32_t); + DECLARE_RH_MULT(uint32_t); + DECLARE_RH_MULT(int64_t); + DECLARE_RH_MULT(uint64_t); + DECLARE_RH_MULT(float); + DECLARE_RH_MULT(double); +# undef DECLARE_LH_MULT + + template + inline auto operator/ (Quantity1 x, Quantity2 y) { + static_assert(quantity_concept); + static_assert(quantity_concept); + + return x.divide(y); + } + +# define DECLARE_LH_DIV(lhtype) \ + template \ + inline auto \ + operator/ (lhtype x, Quantity y) { \ + static_assert(quantity_concept); \ + return y.divide_into(x); \ + } + + DECLARE_LH_DIV(int8_t); + DECLARE_LH_DIV(uint8_t); + DECLARE_LH_DIV(int16_t); + DECLARE_LH_DIV(uint16_t); + DECLARE_LH_DIV(int32_t); + DECLARE_LH_DIV(uint32_t); + DECLARE_LH_DIV(int64_t); + DECLARE_LH_DIV(uint64_t); + DECLARE_LH_DIV(float); + DECLARE_LH_DIV(double); +# undef DECLARE_LH_DIV + +# define DECLARE_RH_DIV(rhtype) \ + template \ + inline auto \ + operator/ (Quantity x, rhtype y) { \ + static_assert(quantity_concept); \ + return x.divide_by(y); \ + } + + DECLARE_RH_DIV(int8_t) + DECLARE_RH_DIV(uint8_t) + DECLARE_RH_DIV(int16_t) + DECLARE_RH_DIV(uint16_t) + DECLARE_RH_DIV(int32_t) + DECLARE_RH_DIV(uint32_t) + DECLARE_RH_DIV(int64_t) + DECLARE_RH_DIV(uint64_t) + DECLARE_RH_DIV(float) + DECLARE_RH_DIV(double) +# undef DECLARE_RH_DIV + + template + inline std::ostream & + operator<< (std::ostream & os, quantity const & x) { + x.display(os); + return os; + } + + namespace qty { + // ----- mass ----- + + template + inline auto milligrams(Repr x) -> quantity { + return quantity::promote(x); + }; + + template + inline auto grams(Repr x) -> quantity { + return quantity::promote(x); + }; + + template + inline auto kilograms(Repr x) -> quantity { + return quantity::promote(x); + }; + + // ----- distance ----- + + template + inline auto millimeters(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto meters(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto kilometers(Repr x) -> quantity { + return quantity::promote(x); + } + + // ----- time ----- + + template + inline auto nanoseconds(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto microseconds(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto milliseconds(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto seconds(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto minutes(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto hours(Repr x) -> quantity { + return quantity::promote(x); + } + + template + inline auto days(Repr x) -> quantity { + return quantity::promote(x); + } + + // ----- time/volatility ----- + + /** quantity in units of 1/sqrt(1dy) **/ + template + inline auto volatility1d(Repr x) -> quantity { + return quantity::promote(x); + } + + /** quantity in units of 1/sqrt(30days) + **/ + template + inline auto volatility30d(Repr x) -> quantity { + return quantity::promote(x); + } + + /** quantity in units of 1/sqrt(250days) + **/ + template + inline auto volatility250d(Repr x) -> quantity { + return quantity::promote(x); + } + } /*namespace qty*/ + } /*namespace obs*/ +} /*namespace xo*/ + +/* end quantity.hpp */ diff --git a/include/xo/unit/quantity_concept.hpp b/include/xo/unit/quantity_concept.hpp new file mode 100644 index 00000000..963a0246 --- /dev/null +++ b/include/xo/unit/quantity_concept.hpp @@ -0,0 +1,21 @@ +/** @file quantity_concept.hpp **/ + +#pragma once + +#include + +namespace xo { + namespace obs { + template + concept quantity_concept = requires(Quantity qty, typename Quantity::repr_type repr) + { + typename Quantity::unit_type; + typename Quantity::repr_type; + + { qty.scale() } -> std::same_as; + { Quantity::unit_cstr() } -> std::same_as; + { Quantity::unit_quantity() } -> std::same_as; + { Quantity::promote(repr) } -> std::same_as; + }; + } /*namespace obs*/ +} /*namespace xo*/ diff --git a/include/xo/unit/ratio_concept.hpp b/include/xo/unit/ratio_concept.hpp new file mode 100644 index 00000000..3308b1f1 --- /dev/null +++ b/include/xo/unit/ratio_concept.hpp @@ -0,0 +1,13 @@ +/* @file ratio_concept.hpp */ + +#pragma once + +#include + +namespace xo { + namespace obs { + template + concept ratio_concept = (std::is_signed_v + && std::is_signed_v); + } /*namespace obs*/ +} /*namespace xo*/ diff --git a/include/xo/unit/ratio_util.hpp b/include/xo/unit/ratio_util.hpp new file mode 100644 index 00000000..7695db3d --- /dev/null +++ b/include/xo/unit/ratio_util.hpp @@ -0,0 +1,242 @@ +/* @file ratio_util.hpp */ + +#pragma once + +#include "ratio_concept.hpp" +#include "stringliteral.hpp" +#include +#include + +namespace xo { + namespace obs { + // ----- ratio_floor ----- + + template + struct ratio_floor { + using type = std::ratio; + }; + + template + using ratio_floor_t = ratio_floor::type; + + // ----- ratio_frac ----- + + template + struct ratio_frac { + static_assert(ratio_concept); + + using type = std::ratio_subtract>::type; + }; + + template + using ratio_frac_t = ratio_frac::type; + + // ----- ratio_power ----- + + /** ratio to an integer power + * + * ratio_power::type + * + * yields a std::ratio representing Base^Power. + **/ + template + struct ratio_power_aux; + + template + struct ratio_power_aux { + + /** std::ratio representing Base^(-Power) **/ + using _inverse_ratio_type = typename ratio_power_aux::type; + + /** Base^(-Power)^-1 = Base^Power **/ + using type = std::ratio_divide, _inverse_ratio_type>; + + static_assert(ratio_concept); + }; + + template + struct ratio_power_aux { + using type = std::ratio<1>; + }; + + template + struct ratio_power_aux { + using type = Base; + }; + + template + struct ratio_power_aux { + /** Base^(Power/2) **/ + using _p2_ratio_type = ratio_power_aux::type; + /** Base^(Power%2) : + * - Base^0 if Power is even + * - Base^1 if Power is odd + **/ + using _x_ratio_type = ratio_power_aux::type; + + using type = std::ratio_multiply< _x_ratio_type, + std::ratio_multiply<_p2_ratio_type, + _p2_ratio_type> >; + + static_assert(ratio_concept); + }; + + // ----- ratio_power_v ----- + + /** compute Base^Power at compile time **/ + template + using ratio_power_t = ratio_power_aux::type; + + // ----- from_ratio ----- + + /** Example: + * from_ratio --> 0.1 + * from_ratio --> 0.8 + **/ + template + constexpr Repr from_ratio() { + static_assert(ratio_concept); + + return Ratio::num / static_cast(Ratio::den); + } + + // ----- ratio2str_aux ----- + + /* note: using struct wrapper because partial specialization of function templates + * is not allowed + */ + template + struct ratio2str_aux; + + template + struct ratio2str_aux { + static constexpr auto value = stringliteral_concat("(", + stringliteral_from_int_v().value_, + "/", + stringliteral_from_int_v().value_, + ")"); + }; + + template + struct ratio2str_aux { + /* note: using struct wrapper because partial specialization of function templates + * is not allowed + */ + static constexpr auto value = stringliteral_concat("-(", + stringliteral_from_int_v<-Num>().value_, + "/", + stringliteral_from_int_v().value_, + ")"); + }; + + template + struct ratio2str_aux { + static constexpr auto value = stringliteral_concat("-", + stringliteral_from_int_v<-Num>().value_); + }; + + template + struct ratio2str_aux { + static constexpr auto value = stringliteral_from_int_v(); + }; + + template <> + struct ratio2str_aux<1, 1, false> { + static constexpr auto value = stringliteral(""); + }; + + // ----- stringliteral_from_ratio ----- + + /** Example: + * - stringliteral_from_ratio -> "^(2,3)" + **/ + template + constexpr auto stringliteral_from_ratio() { + static_assert(ratio_concept); + + using lowest_terms_type = Ratio::type; + + return ratio2str_aux().value; + }; + + template + constexpr char const * cstr_from_ratio() { + static_assert(ratio_concept); + + using lowest_terms_type = Ratio::type; + + return ratio2str_aux().value.c_str(); + }; + + // ----- exponent2str_aux ----- + + /* note: using struct wrapper because partial specialization of function templates + * is not allowed + */ + template + struct exponent2str_aux; + + template + struct exponent2str_aux { + static constexpr auto value = stringliteral_concat("^(", + stringliteral_from_int_v().value_, + "/", + stringliteral_from_int_v().value_, + ")"); + }; + + template + struct exponent2str_aux { + /* note: using struct wrapper because partial specialization of function templates + * is not allowed + */ + static constexpr auto value = stringliteral_concat("^-(", + stringliteral_from_int_v<-Num>().value_, + "/", + stringliteral_from_int_v().value_, + ")"); + }; + + template + struct exponent2str_aux { + static constexpr auto value = stringliteral_concat("^", + stringliteral_from_int_v().value_); + }; + + template + struct exponent2str_aux { + /* Example: + * - exponent2str_aux<-1, 1, true> --> "^-1" + * - exponent2str_aux<-2, 1, true> --> "^-2" + */ + static constexpr auto value = stringliteral_concat("^", + stringliteral_from_int_v().value_); + }; + + template <> + struct exponent2str_aux<1, 1, false> { + static constexpr auto value = stringliteral(""); + }; + + // ----- stringliteral_from_exponent ----- + + template + constexpr auto stringliteral_from_exponent() { + static_assert(ratio_concept); + + return exponent2str_aux().value; + }; + + } /*namespace obs*/ +} /*namespace xo*/ + +/* end ratio_util.hpp */ diff --git a/include/xo/unit/stringliteral.hpp b/include/xo/unit/stringliteral.hpp new file mode 100644 index 00000000..c6bc6243 --- /dev/null +++ b/include/xo/unit/stringliteral.hpp @@ -0,0 +1,170 @@ +/* @file stringliteral.hpp */ + +#pragma once + +#include +#include +#include +#include + +namespace xo { + namespace obs { + + // ----- stringliteral ----- + + /** @class stringliteral + * + * @brief class to represent a literal string at compile time, for use as template argument + **/ + template + struct stringliteral { + constexpr stringliteral() { if (N > 0) value_[0] = '\0'; } + constexpr stringliteral(const char (&str)[N]) { std::copy_n(str, N, value_); } + constexpr int size() const { return N; } + + constexpr char const * c_str() const { return value_; } + + char value_[N]; + }; + + template < typename First, typename... Rest > + constexpr auto + all_same_v = std::conjunction_v< std::is_same... >; + + /** args and result must be stringliteral **/ + template < typename... Ts> + constexpr auto + stringliteral_concat(Ts && ... args) + { +#ifdef NOT_USING + static_assert(all_same_v...>, + "string must share the same char type"); + + using char_type = std::remove_const_t< std::remove_pointer_t < std::common_type_t < Ts... > > >; +#endif + using char_type = char; + + /** n1: total number of bytes used by arguments **/ + constexpr size_t n1 = (sizeof(Ts) + ...); + /** z1: each string arg has a null terminator included in its size, + * z1 is the number of arguments in parameter pack Ts, + * which equals the number of null terminators used + **/ + constexpr size_t z1 = sizeof...(Ts); + + /** n: number of chars in concatenated string. +1 for final null **/ + constexpr size_t n + = (n1 / sizeof(char_type)) - z1 + 1; + + stringliteral result; + + size_t pos = 0; + + auto detail_concat = [ &pos, &result ](auto && arg) { + constexpr auto count = (sizeof(arg) - sizeof(char_type)) / sizeof(char_type); + + std::copy_n(arg, count, result.value_ + pos); + pos += count; + }; + + (detail_concat(args), ...); + + return result; + } + + template + constexpr auto + stringliteral_compare(stringliteral && s1, stringliteral && s2) + { + return std::string_view(s1.value_) <=> std::string_view(s2.value_); + } + + // ----- literal_size ----- + + /** @brief compute number of chars needed to stringify an int **/ + template < int d, bool signbit = std::signbit(d) > + struct literal_size; + + template < int d > + struct literal_size { + /* d < 0 */ + static constexpr int size = 1 + literal_size<-d, false>::size; + }; + + template < int d > + struct literal_size { + /* d >= 0 */ + static constexpr int size = 1 + literal_size::size; + }; + + template <> struct literal_size<0, false> { static constexpr int size = 1; }; + template <> struct literal_size<1, false> { static constexpr int size = 1; }; + template <> struct literal_size<2, false> { static constexpr int size = 1; }; + template <> struct literal_size<3, false> { static constexpr int size = 1; }; + template <> struct literal_size<4, false> { static constexpr int size = 1; }; + template <> struct literal_size<5, false> { static constexpr int size = 1; }; + template <> struct literal_size<6, false> { static constexpr int size = 1; }; + template <> struct literal_size<7, false> { static constexpr int size = 1; }; + template <> struct literal_size<8, false> { static constexpr int size = 1; }; + template <> struct literal_size<9, false> { static constexpr int size = 1; }; + + template < int d > + constexpr int literal_size_v = literal_size::size; + + // ----- literal_from_digit ----- + + constexpr auto /*stringliteral<2>*/ stringliteral_from_digit( int d ) { + return stringliteral({ static_cast('0' + d), '\0' }); + } + +#ifdef NOT_YET_22mar24 + template < int d > + struct literal_from_digit; + + template <> struct literal_from_digit<0> { static constexpr auto value = stringliteral("0"); }; + template <> struct literal_from_digit<1> { static constexpr auto value = stringliteral("1"); }; + template <> struct literal_from_digit<2> { static constexpr auto value = stringliteral("2"); }; + template <> struct literal_from_digit<3> { static constexpr auto value = stringliteral("3"); }; + template <> struct literal_from_digit<4> { static constexpr auto value = stringliteral("4"); }; + template <> struct literal_from_digit<5> { static constexpr auto value = stringliteral("5"); }; + template <> struct literal_from_digit<6> { static constexpr auto value = stringliteral("6"); }; + template <> struct literal_from_digit<7> { static constexpr auto value = stringliteral("7"); }; + template <> struct literal_from_digit<8> { static constexpr auto value = stringliteral("8"); }; + template <> struct literal_from_digit<9> { static constexpr auto value = stringliteral("9"); }; + + template < int d > + constexpr auto literal_from_digit_v() { return literal_from_digit::value; } +#endif + + // ----- stringliteral_from_int ----- + + template < int D, int N = literal_size_v, bool signbit = std::signbit(D) > + struct stringliteral_from_int; + + template < int D, int N = literal_size_v, bool signbit = std::signbit(D) > + constexpr auto stringliteral_from_int_v() { return stringliteral_from_int::value; } + + template < int D > + struct stringliteral_from_int< D, 1, false > { + static constexpr auto value = stringliteral_from_digit(D); + }; + + template < int D, int N > + struct stringliteral_from_int< D, N, false > { + static constexpr auto _prefix = stringliteral_from_int_v< D / 10, N - 1, false >(); + static constexpr auto _suffix = stringliteral_from_digit(D % 10); + + static constexpr auto value = stringliteral_concat(_prefix.value_, _suffix.value_); + }; + + template < int D, int N > + struct stringliteral_from_int< D, N, true > { + static constexpr auto _suffix = stringliteral_from_int_v< -D, N - 1, false>(); + + static constexpr auto value = stringliteral_concat("-", _suffix.value_); + }; + + } /*namespace obs*/ +} /*namespace xo*/ + +/* end stringliteral.hpp */ diff --git a/include/xo/unit/unit.hpp b/include/xo/unit/unit.hpp new file mode 100644 index 00000000..d081077e --- /dev/null +++ b/include/xo/unit/unit.hpp @@ -0,0 +1,394 @@ +/* @file dimension.hpp */ + +#pragma once + +#include "dimension_impl.hpp" + +namespace xo { + namespace obs { + // ----- wrap_unit ----- + + template + struct wrap_unit { + static_assert(ratio_concept); + static_assert(bpu_list_concept); + + //using _norm_type = bpu_normalize; + + using scalefactor_type = Scalefactor; + using dim_type = BpuList; + + /* canon_type just orders dimensions by increasing native_dim_id */ + using canon_type = canonical_impl::dim_type; + + static_assert(bpu_list_concept); + }; + + // ----- normalize_unit ----- + + template + struct normalize_unit { + static_assert(unit_concept); + + using type = wrap_unit, typename Unit::dim_type>; + }; + + template + using normalize_unit_t = normalize_unit::type; + + // ----- dimensionless_v ----- + + template + constexpr auto dimensionless_v = std::same_as; + + // ----- unit_find_bpu ----- + + /** @brief find basis-power-unit matching native_dim_id + * + * Constructs dimensionless native_bpu if no match + **/ + template + struct unit_find_bpu { + using type = di_find_bpu::type; + }; + + template + using unit_find_bpu_t = unit_find_bpu::type; + + // ----- unit_abbrev_v ----- + + /** @brief canonical stringliteral abbreviation for dimension D. **/ + template + constexpr auto unit_abbrev_v = di_assemble_abbrev::value; + + // ----- unit_invert ----- + + template + struct unit_invert { + static_assert(unit_concept); + + using _sf = std::ratio_divide, typename U::scalefactor_type>; + using _di = di_invert< typename U::dim_type >::type; + + using type = wrap_unit< _sf, _di >; + + static_assert(unit_concept); + }; + + template + using unit_invert_t = unit_invert::type; + + // ----- unit_cartesian_product ----- + + template + struct unit_cartesian_product { + /* when a basis dimension (represented by a bpu<..>::c_native_dim) + * is present in both {U1, U2}, we need to pick a common unit + * (represented by bpu<..>::scalefactor_type). + * + * scale factors from such conversions are collected in: + * 1a. _mult_type::outer_scalefactor_type (compile-time exact representation using std::ratio) + * 1b. _mult_type::outer_scalefactor_inexact (compile-time constexpr) + */ + + static_assert(unit_concept); + static_assert(unit_concept); + + /* _mult_type -> describes product dimension */ + using _mult_type = di_cartesian_product< + typename U1::dim_type, + typename U2::dim_type>; + + /* compile-time exact scalefactor for product dimension + * (distilled from any forced rescaling) + */ + using _mult_sf_type = _mult_type::outer_scalefactor_type; + /* bpulist specifying basis factors (possibly to rational powers) in product dimension */ + using _mult_di_type = _mult_type::bpu_list_type; + + /* note: inexact scalefactor doesn't come up here. + * It's not present in unit types, only appears as byproduct + * of products/ratios of units + */ + using _sf1_type = typename std::ratio_multiply< + typename U1::scalefactor_type, + typename U2::scalefactor_type>::type; + + using _sf_type = typename std::ratio_multiply<_mult_sf_type, _sf1_type>::type; + + /* note: we can compute inexact scale factor, + * but can't make it a template argument + */ + using exact_unit_type = wrap_unit< _sf_type, _mult_di_type >; + + static constexpr double c_scalefactor_inexact = _mult_type::c_outer_scalefactor_inexact; + + static_assert(unit_concept); + }; + + /* WARNING: omits inexact scalefactor */ + template + using unit_cartesian_product_t = unit_cartesian_product::exact_unit_type; + + // ----- unit_divide ----- + + template + struct unit_divide { + static_assert(unit_concept); + static_assert(unit_concept); + + using _mult_type = unit_cartesian_product>; + using exact_unit_type = _mult_type::exact_unit_type; + + static constexpr double c_scalefactor_inexact = _mult_type::c_scalefactor_inexact; + }; + + /* WARNING: omits inexact scalefactor */ + template + using unit_divide_t = unit_divide::exact_unit_type; + + // ----- same_dimension ----- + + /* true iff U1 and U2 represent the same dimension, i.e. differ only in dimensionless scaling factor + * + * To verify scale also, use same_unit instead + */ + template + struct same_dimension { + static_assert(unit_concept); + static_assert(unit_concept); + + using _unit_ratio_type = typename unit_cartesian_product>::exact_unit_type; + + static_assert(std::same_as); + + static constexpr bool value = std::same_as; + }; + + template + constexpr bool same_dimension_v = same_dimension::value; + + // ----- same_unit ----- + + template + struct same_unit { + static_assert(unit_concept); + static_assert(unit_concept); + + using _unit_ratio_type = unit_cartesian_product>; + using _unit_exact_type = typename _unit_ratio_type::exact_unit_type; + using _unit_scalefactor_type = _unit_exact_type::scalefactor_type; + static constexpr double c_unit_ratio_inexact = _unit_ratio_type::c_scalefactor_inexact; + + static_assert(std::same_as<_unit_scalefactor_type, std::ratio<1>>); + static_assert(std::same_as); + + static constexpr bool value = (std::same_as<_unit_scalefactor_type, std::ratio<1>> + && (c_unit_ratio_inexact == 1.0) + && std::same_as); + }; + + template + constexpr bool same_unit_v = same_unit::value; + + // ----- unit_conversion_factor ----- + + template + struct unit_conversion_factor { + static_assert(same_dimension_v); + + using _unit_ratio_type = typename unit_cartesian_product>::exact_unit_type; + using type = _unit_ratio_type::scalefactor_type; + static constexpr double c_scalefactor_inexact = _unit_ratio_type::c_scalefactor_inexact; + }; + + /** conversion factor from U1 to U2: + * U1 = x.U2 + * with: + * x = R::num / R::den + * R = unit_conversion_factor_t + * + * WARNING: omits inexact scalefactor unit_conversion_factor::c_scalefactor_inexact + **/ + template + using unit_conversion_factor_t = unit_conversion_factor::type; + + // ----- units ----- + + namespace units { + /* computing abbreviations: + * - unit_abbrev_v :: stringliteral<...> + * - unit_abbrev_v.c_str() :: const char * + * + * relies on + * - di_assemble_abbrev, di_assemble_abbrev_helper [dimension_impl.hpp] + * + * - bpu_assemble_abbrev() [native_bpu.hpp] + * - bpu_assemble_abbrev_helper< native_bpu::c_native_dim, + * native_bpu::scalefactor_type, + * native_bpu::power_type > + * -> stringliteral + * + * + can specialize for specific combinations + * + * - native_unit_abbrev_helper< native_bpu::c_native_dim, + * native_bpu::power_type > + */ + + // ----- weight ----- + + using milligram = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("mg"); + }; + + using gram = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + using kilogram = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("kg"); + }; + + // ----- distance ----- + + using millimeter = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("mm"); + }; + + using meter = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + using kilometer = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("km"); + }; + + // ----- time ----- + + using nanosecond = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("ns"); + }; + + using microsecond = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("us"); + }; + + using millisecond = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("ms"); + }; + + using second = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; + using minute = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("min"); + }; + + using hour = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("hr"); + }; + + using day = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("dy"); + }; + + using month = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("mo"); + }; + + using yr250 = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + + template <> + struct scaled_native_unit_abbrev> { + static constexpr auto value = stringliteral("yr250"); + }; + + // ------ volatility ------ + + /* volatility in units of 1/sqrt(1day) + * volatility^2 in units of 1/day + */ + using volatility_1d = wrap_unit< std::ratio<1>, + bpu_node< bpu, + std::ratio<-1,2>> > >; + + /* volatility in units of 1/sqrt(30day) + * volatility^2 in units of 1/(30day) + */ + using volatility_30d = wrap_unit< std::ratio<1>, + bpu_node< bpu, + std::ratio<-1,2>> > >; + + /* volatility in units of 1/sqrt(250day) + * volatility^2 in units of 1/(250day) + */ + using volatility_250d = wrap_unit< std::ratio<1>, + bpu_node< bpu, + std::ratio<-1,2>> > >; + + using currency = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + using price = wrap_unit< std::ratio<1>, + bpu_node< bpu> > >; + } + + } /*namespace obs*/ +} /*namespace xo*/ + + +/* end dimension.hpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..97d1afa1 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,25 @@ +# build unittest observable/utest + +set(SELF_EXECUTABLE_NAME utest.unit) +set(SELF_SOURCE_FILES + unit_utest_main.cpp + unit.test.cpp quantity.test.cpp) + +add_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) +xo_include_options2(${SELF_EXECUTABLE_NAME}) + +add_test(NAME ${SELF_EXECUTABLE_NAME} COMMAND ${SELF_EXECUTABLE_NAME}) +target_code_coverage(${SELF_EXECUTABLE_NAME} AUTO ALL) + +# ---------------------------------------------------------------- +# internal dependencies: logutil, ... + +xo_self_dependency(${SELF_EXECUTABLE_NAME} xo_unit) +xo_dependency(${SELF_EXECUTABLE_NAME} reflect) + +# ---------------------------------------------------------------- +# 3rd party dependency: catch2: + +xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) + +# end CMakeLists.txt diff --git a/utest/quantity.test.cpp b/utest/quantity.test.cpp new file mode 100644 index 00000000..8274012c --- /dev/null +++ b/utest/quantity.test.cpp @@ -0,0 +1,653 @@ +/* @file quantity.test.cpp */ + +#include "xo/unit/quantity.hpp" +#include "xo/reflect/Reflect.hpp" +//#include +//#include +#include +#include +#include + +namespace xo { + using xo::obs::quantity; + + using xo::obs::qty::milliseconds; + using xo::obs::qty::seconds; + using xo::obs::qty::minutes; + using xo::obs::qty::volatility30d; + using xo::obs::qty::volatility250d; + + using xo::obs::unit_find_bpu_t; + using xo::obs::unit_conversion_factor_t; + using xo::obs::unit_cartesian_product_t; + using xo::obs::unit_cartesian_product; + using xo::obs::unit_invert_t; + using xo::obs::unit_abbrev_v; + using xo::obs::same_dimension_v; + using xo::obs::dim; + + using xo::obs::from_ratio; + using xo::obs::stringliteral_from_ratio; + using xo::obs::ratio2str_aux; + using xo::obs::cstr_from_ratio; + + using xo::reflect::Reflect; + + namespace units = xo::obs::units; + + namespace ut { + /* use 'testcase' snippet to add test cases here */ + TEST_CASE("quantity", "[quantity]") { + //constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + //scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.quantity"), xtag("foo", foo), ...); + //log && log("(A)", xtag("foo", foo)); + + quantity t = seconds(1L); + + REQUIRE(t.scale() == 1); + + static_assert(t.c_basis_power == 1); + static_assert(t.c_basis_power == 0); + } /*TEST_CASE(quantity)*/ + + TEST_CASE("add1", "[quantity]") { + constexpr bool c_debug_flag = false; + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add1")); + + quantity t1 = seconds(1); + quantity t2 = seconds(2); + + static_assert(std::same_as); + static_assert(std::same_as); + + auto sum = t1 + t2; + + CHECK(strcmp(sum.unit_cstr(), "s") == 0); + + static_assert(std::same_as); + static_assert(t1.c_basis_power == 1); + static_assert(t2.c_basis_power == 1); + + REQUIRE(sum.scale() == 3); + + } /*TEST_CASE(add1)*/ + + TEST_CASE("add2", "[quantity]") { + constexpr bool c_debug_flag = false; + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add2")); + + quantity t1 = seconds(1); + { + CHECK(strcmp(t1.unit_cstr(), "s") == 0); + CHECK(t1.scale() == 1); + } + + auto m2 = minutes(2); + + { + static_assert(m2.c_basis_power == 1); + + log && log(xtag("m2.scale", m2.scale()), xtag("m2.unit", m2.unit_cstr())); + + CHECK(m2.scale() == 2); + CHECK(strcmp(m2.unit_cstr(), "min") == 0); + } + + { + auto m2_sec = m2.in_units_of(); + + static_assert(std::same_as); + + log && log(XTAG(m2_sec)); + + CHECK(m2_sec == 120); + } + + quantity t2 = m2; + { + auto sum = t1 + t2; + + static_assert(std::same_as); + static_assert(sum.c_basis_power == 1); + + log && log(xtag("t1.unit", t1.unit_cstr()), xtag("t2.unit", t2.unit_cstr())); + log && log(xtag("sum.unit", sum.unit_cstr())); + + CHECK(strcmp(t2.unit_cstr(), "s") == 0); + CHECK(strcmp(sum.unit_cstr(), "s") == 0); + CHECK(sum.scale() == 121); + } + } /*TEST_CASE(add2)*/ + + TEST_CASE("add3", "[quantity]") { + quantity t1 = seconds(1); + quantity t2 = minutes(2); + + /* sum will take unit from lhs argument to + */ + auto sum = t1 + t2; + + static_assert(sum.c_basis_power == 1); + static_assert(std::same_as); + + REQUIRE(sum.scale() == 121); + } /*TEST_CASE(add3)*/ + + TEST_CASE("add4", "[quantity]") { + constexpr bool c_debug_flag = false; + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add4")); + + using u_kgps_result = unit_cartesian_product>; + using u_kgps = u_kgps_result::exact_unit_type; + using u_gpm_result = unit_cartesian_product>; + using u_gpm = u_gpm_result::exact_unit_type; + { + static_assert(u_kgps_result::c_scalefactor_inexact == 1.0); + + static_assert(std::same_as::power_type, std::ratio<1>>); + static_assert(std::same_as::power_type, std::ratio<-1>>); + static_assert(std::same_as::power_type, std::ratio<1>>); + static_assert(std::same_as::power_type, std::ratio<-1>>); + + log && log(xtag("u_kgps", unit_abbrev_v.c_str())); + log && log(xtag("u_gpm", unit_abbrev_v.c_str())); + + CHECK(strcmp(unit_abbrev_v.c_str(), "kg.s^-1") == 0); + CHECK(strcmp(unit_abbrev_v.c_str(), "g.min^-1") == 0); + + static_assert(same_dimension_v); + } + + using convert_type = unit_conversion_factor_t; + { + log && log(xtag("u_kgps->u_gpm", cstr_from_ratio())); + + CHECK(strcmp(cstr_from_ratio(), "60000") == 0); + CHECK(from_ratio() == 60000); + } + + /* note: in practice probably write + * kilograms(0.1) / seconds(1); + * but + * 1. don't want to exercise quantity {*,/} here; + * 2. want to force unit representation + */ + auto q1 = quantity::promote(0.1); + auto q2 = quantity(); + { + q2 = q1; + + static_assert(q1.c_basis_power == 1); + static_assert(q1.c_basis_power == -1); + static_assert(q2.c_basis_power == 1); + static_assert(q2.c_basis_power == -1); + + log && log(XTAG(q1), XTAG(q2)); + + CHECK(strcmp(q1.unit_cstr(), "kg.s^-1") == 0); + CHECK(q1.scale() == 0.1); + + CHECK(strcmp(q2.unit_cstr(), "g.min^-1") == 0); + CHECK(q2.scale() == 6000.0); + } + } /*TEST_CASE(add4)*/ + + TEST_CASE("add5", "[quantity][fractional_dimension]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.add5")); + //log && log("(A)", xtag("foo", foo)); + + auto vol_250d = volatility250d(0.2); + { + log && log(xtag("vol_250d", vol_250d)); + + CHECK(strcmp(vol_250d.unit_cstr(), "yr250^-(1/2)") == 0); + CHECK(vol_250d.scale() == 0.2); + } + + /* scaling factor from 30-day vol to 250-day vol is sqrt(250/30) ~ 2.88675 + * so 0.1 -> 0.288675 + */ + auto vol_30d = volatility30d(0.1); + { + log && log(xtag("vol_30d", vol_30d)); + + CHECK(strcmp(vol_30d.unit_cstr(), "mo^-(1/2)") == 0); + CHECK(vol_30d.scale() == Approx(0.1).epsilon(1e-6)); + } + + /* conversion from monthly vol to (250-day) annual vol */ + + using u_vol250d = units::volatility_250d; + { + quantity q = vol_30d; + + log && log(xtag("q", q)); + + CHECK(strcmp(q.unit_cstr(), "yr250^-(1/2)") == 0); + CHECK(q.scale() == Approx(0.288675).epsilon(1e-6)); + + } + + { + auto sum = vol_250d + vol_30d; + + static_assert(sum.c_basis_power == -0.5); + + log && log(XTAG(sum)); + + CHECK(strcmp(sum.unit_cstr(), "yr250^-(1/2)") == 0); + /* 0.1mo^-(1/2) ~ 0.288675yr250^-(1/2) */ + CHECK(sum.scale() == Approx(0.4886751).epsilon(1e-6)); + } + } /*TEST_CASE(add5)*/ + + + TEST_CASE("mult1", "[quantity]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.mult1")); + //log && log("(A)", xtag("foo", foo)); + + auto q0 = milliseconds(5); + auto q1 = seconds(60); + auto q2 = minutes(1); + + { + auto r = q0 * q1; + + static_assert(r.c_basis_power == 2); + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0*q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* taking unit from LHS */ + REQUIRE(strcmp(r.unit_cstr(), "ms^2") == 0); + REQUIRE(r.scale() == 300000); + } + + { + auto r = q1 * q2; + + static_assert(r.c_basis_power == 2); + + log && log(xtag("q1", q1), xtag("q2", q2), xtag("q1*q2", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* taking unit from LHS */ + REQUIRE(strcmp(r.unit_cstr(), "s^2") == 0); + REQUIRE(r.scale() == 3600); + } + + { + auto r = q2 * q1; + + static_assert(r.c_basis_power == 2); + + log && log(xtag("q1", q1), xtag("q2", q2), xtag("r=q2*q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* taking unit from LHS */ + CHECK(strcmp(r.unit_cstr(), "min^2") == 0); + CHECK(r.scale() == 1); + } + + { + auto r = q2 * 60; + + static_assert(r.c_basis_power == 1); + static_assert(std::same_as); + + log && log(xtag("q2*60", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* preserve units of existing quantity */ + CHECK(strcmp(r.unit_cstr(), "min") == 0); + CHECK(r.scale() == 60); + } + + { + auto r = q2 * 60U; + + static_assert(r.c_basis_power == 1); + static_assert(std::same_as); + + log && log(xtag("q2*60U", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* preserve units of existing quantity */ + CHECK(strcmp(r.unit_cstr(), "min") == 0); + CHECK(r.scale() == 60U); + } + + { + auto r = (q2 * 60.5); + + static_assert(r.c_basis_power == 1); + + /* verify dimension */ + static_assert(std::same_as); + + log && log(xtag("q2*60.5", q2*60.5)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* preserve units of existing quantity */ + REQUIRE(strcmp(r.unit_cstr(), "min") == 0); + REQUIRE(r.scale() == 60.5); + } + + { + log && log(xtag("q2*60.5f", q2*60.5f)); + + auto r = (q2 * 60.5f); + + /* verify dimension */ + static_assert(r.c_basis_power == 1); + static_assert(std::same_as); + + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* preserve units of existing quantity */ + REQUIRE(strcmp(r.unit_cstr(), "min") == 0); + REQUIRE(r.scale() == 60.5f); + } + + { + auto r = 60 * q2; + + static_assert(r.c_basis_power == 1); + static_assert(std::same_as); + + log && log(xtag("60*q2", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* preserve units of existing quantity */ + CHECK(strcmp(r.unit_cstr(), "min") == 0); + CHECK(r.scale() == 60); + } + + { + log && log(xtag("60.5*q2", 60.5*q2)); + + auto r = 60.5 * q2; + + static_assert(r.c_basis_power == 1); + + log && log(xtag("r.type", Reflect::require()->canonical_name())); + static_assert(std::same_as); + + /* preserve units of existing quantity */ + CHECK(strcmp(r.unit_cstr(), "min") == 0); + CHECK(r.scale() == 60.5); + } + + { + log && log(xtag("60.5f*q2", 60.5f*q2)); + + auto r = 60.5f * q2; + + static_assert(r.c_basis_power == 1); + static_assert(std::same_as); + + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* preserve units of existing quantity */ + CHECK(strcmp(r.unit_cstr(), "min") == 0); + CHECK(r.scale() == 60.5); + } + } /*TEST_CASE(mult1)*/ + + TEST_CASE("div1", "[quantity]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div1")); + //log && log("(A)", xtag("foo", foo)); + + auto q0 = milliseconds(5); + auto q1 = milliseconds(10); + + { + /* repr_type adopts argument to milliseconds() */ + static_assert(std::same_as); + static_assert(std::same_as); + + auto r = q0/q1; + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimensionless + no type promotion */ + static_assert(std::same_as); + /* verify scale (truncate)*/ + REQUIRE(r == 0); + } + + auto q0p = milliseconds(5.0); + + { + static_assert(std::same_as); + + auto r = q0p/q1; + static_assert(std::same_as); + + log && log(XTAG(q0p), xtag("q0p/q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(std::same_as); + + /* verify scale */ + REQUIRE(r == 0.5); + } + + auto r1 = 1.0 / q0; + + { + log && log(XTAG(q0), xtag("r1=1.0/q0", r1)); + + /* verify dimension */ + static_assert(r1.c_basis_power == -1); + + /* verify scale */ + REQUIRE(r1.scale() == 0.2); + } + } /*TEST_CASE(div1)*/ + + TEST_CASE("div2", "[quantity]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div2")); + + auto q0 = milliseconds(5); + auto q1 = milliseconds(20.0); + + { + auto r = q0/q1; + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(std::same_as); + + /* verify scale */ + REQUIRE(r == 0.25); + } + + { + auto r = q1/q0; + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("q1/q0", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(std::same_as); + + /* verify scale */ + REQUIRE(r == 4.0); + } + + { + auto r = q0/(q1*q1); + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/(q1*q1)", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(r.c_basis_power == -1); + + /* verify scale */ + REQUIRE(r.scale() == 0.0125); + } + + { + auto r = (q0*q0)/q1; + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("(q0*q0)/q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(r.c_basis_power == 1); + + /* verify scale */ + REQUIRE(r.scale() == 1.25); + } + + } /*TEST_CASE(div2)*/ + + TEST_CASE("div3", "[quantity]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div3")); + + auto q0 = milliseconds(5); + auto q1 = milliseconds(20.0); + + { + auto r = q0/q1; + + log && log(XTAG(q0), XTAG(q1), xtag("q0/q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(std::same_as); + + /* verify scale */ + REQUIRE(r == 0.25); + } + + { + auto r = q1/q0; + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("q1/q0", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(std::same_as); + + /* verify scale */ + REQUIRE(r == 4.0); + } + + { + auto r = q0/(q1*q1); + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0/(q1*q1)", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(r.c_basis_power == -1); + + /* verify scale */ + REQUIRE(r.scale() == 0.0125); + } + + { + auto r = (q0*q0)/q1; + + log && log(xtag("q0", q0), xtag("q1", q1), xtag("(q0*q0)/q1", r)); + log && log(xtag("r.type", Reflect::require()->canonical_name())); + + /* verify dimension */ + static_assert(r.c_basis_power == 1); + + /* verify scale */ + REQUIRE(r.scale() == 1.25); + } + + } /*TEST_CASE(div3)*/ + + TEST_CASE("div4", "[quantity]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.mult2")); + //log && log("(A)", xtag("foo", foo)); + + auto q1 = volatility250d(0.2); + auto q2 = volatility30d(0.1); + + auto r = q1/q2; + + /* 0.1/sqrt(30dy) ~ 0.288675/sqrt(250dy), + * so q1/q2 ~ 0.6928 + */ + + log && log(XTAG(q1), XTAG(q2), XTAG(q1/q2)); + + /* verify dimensionless result */ + static_assert(std::same_as); + + /* verify scale of result */ + CHECK(r == Approx(0.692820323).epsilon(1e-6)); + + } /*TEST_CASE(div4)*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end quantity.test.cpp */ diff --git a/utest/unit.test.cpp b/utest/unit.test.cpp new file mode 100644 index 00000000..bccc5d47 --- /dev/null +++ b/utest/unit.test.cpp @@ -0,0 +1,346 @@ +/* @file dimension.test.cpp */ + +#include "xo/unit/unit.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/cxxutil/demangle.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tag.hpp" +#include +#include + +namespace xo { + namespace ut { + /* compile-time tests */ + + using xo::reflect::Reflect; + + using xo::obs::dim; + using xo::obs::native_unit_abbrev_v; + using xo::obs::units::scaled_native_unit_abbrev_v; + //using xo::obs::native_dim_abbrev; + using xo::obs::stringliteral_compare; + using xo::obs::literal_size_v; + using xo::obs::stringliteral_from_digit; + using xo::obs::stringliteral_from_int_v; + using xo::obs::stringliteral; +#ifndef __clang__ + using xo::obs::stringliteral_concat; + using xo::obs::stringliteral_from_ratio; + using xo::obs::bpu_assemble_abbrev_helper; + using xo::obs::bpu_assemble_abbrev; +#endif + using xo::obs::bpu_node; + using xo::obs::wrap_unit; + using xo::obs::unit_abbrev_v; + //using xo::obs::dim_abbrev_v; + using xo::obs::di_cartesian_product; + using xo::obs::di_cartesian_product1; + using xo::obs::unit_cartesian_product_t; + using xo::obs::bpu_cartesian_product; + using xo::obs::bpu_cartesian_product_helper; + using xo::obs::unit_invert_t; + using xo::obs::units::gram; + using xo::obs::units::second; + using xo::print::ccs; + + template + int unused() + { + return 1; + } + + template + constexpr bool unused_same(typename std::enable_if_t::value, bool> result = true) + { + return result; + } + + TEST_CASE("native_unit_abbrev", "[native_dim_abbrev]") { + constexpr bool c_debug_flag = true; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.native_dim_abbrev")); + //log && log("(A)", xtag("foo", foo)); + + /* NOTE: the .value_ expression below will fail to compile if missing specialization for + * native_dim_abbrev on native_dim_id::foo; that's the point :) + */ + + REQUIRE(strcmp(scaled_native_unit_abbrev_v>.value_, "g") == 0); + REQUIRE(strcmp(scaled_native_unit_abbrev_v>.value_, "s") == 0); + REQUIRE(strcmp(scaled_native_unit_abbrev_v>.value_, "ccy") == 0); + REQUIRE(strcmp(scaled_native_unit_abbrev_v>.value_, "px") == 0); + +#ifdef OBSOLETE + REQUIRE(strcmp(native_dim_abbrev().value_, "") != 0); + REQUIRE(strcmp(native_dim_abbrev().value_, "") != 0); + REQUIRE(strcmp(native_dim_abbrev().value_, "") != 0); + REQUIRE(strcmp(native_dim_abbrev().value_, "") != 0); +#endif + + static_assert(stringliteral_compare(stringliteral_from_digit(0), stringliteral("0")) == 0); + static_assert(stringliteral_compare(stringliteral_from_digit(1), stringliteral("1")) == 0); + static_assert(stringliteral_compare(stringliteral_from_digit(9), stringliteral("9")) == 0); + + static_assert(literal_size_v<0> == 1); + static_assert(literal_size_v<10> == 2); + static_assert(literal_size_v<99> == 2); + static_assert(literal_size_v<100> == 3); + static_assert(literal_size_v<999> == 3); + + static_assert(stringliteral_compare(stringliteral_from_int_v<0>(), stringliteral("0")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<9>(), stringliteral("9")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<1, 1, false>(), stringliteral("1")) == 0); + + + static_assert(stringliteral_compare(stringliteral_from_int_v<9, 1, false>(), stringliteral("9")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<9>(), stringliteral("9")) == 0); + + /* NOTE: clang16 complains starting here; gcc is fine */ + +#ifndef __clang__ + if constexpr (stringliteral_concat("a", "b").size() == 3) { + REQUIRE(true); + } else { + REQUIRE(false); + } + + static_assert(stringliteral_compare(stringliteral_concat("hello", " ", "world"), + stringliteral("hello world")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<10, 2, false>(), stringliteral("10")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<10>(), stringliteral("10")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<99, 2, false>(), stringliteral("99")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<99>(), stringliteral("99")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<100, 3, false>(), stringliteral("100")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<100>(), stringliteral("100")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<999, 3, false>(), stringliteral("999")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<999>(), stringliteral("999")) == 0); + + //std::cerr << "test=" << stringliteral_from_int_v<-1, 2, true>().value_ << std::endl; + + static_assert(stringliteral_compare(stringliteral_from_int_v<-1, 2, true>(), stringliteral("-1")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<-1>(), stringliteral("-1")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<-9, 2, true>(), stringliteral("-9")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<-9>(), stringliteral("-9")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<-10, 3, true>(), stringliteral("-10")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<-10>(), stringliteral("-10")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<-99, 3, true>(), stringliteral("-99")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<-99>(), stringliteral("-99")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<-100, 4, true>(), stringliteral("-100")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<-100>(), stringliteral("-100")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_int_v<-999, 4, true>(), stringliteral("-999")) == 0); + static_assert(stringliteral_compare(stringliteral_from_int_v<-999>(), stringliteral("-999")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("(2/3)")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("(2/3)")) == 0); + + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("-1")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("-2")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("-2")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("-(3/2)")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("-(3/2)")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("-(1/2)")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("(1/2)")) == 0); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("(3/2)")) == 0); + + //log && log(xtag("ratio<2>", stringliteral_from_ratio>().c_str())); + static_assert(stringliteral_compare(stringliteral_from_ratio>(), stringliteral("2")) == 0); + + static_assert(stringliteral_compare(bpu_assemble_abbrev_helper, std::ratio<1>>(), stringliteral("g")) == 0); + //log && log(xtag("s^(-1/2)", bpu_assemble_abbrev_helper, std::ratio<-1,2>>().c_str())); + static_assert(stringliteral_compare(bpu_assemble_abbrev_helper, std::ratio<-1,2>>(), stringliteral("s^-(1/2)")) == 0); + //stringliteral_compare(stringliteral_from_ratio>(), stringliteral("^2")) == 0); +#endif + + //static_assert(stringliteral_compare(stringliteral_from_int_v<10>(), obs::stringliteral("10")) == 0); + + //REQUIRE(strcmp(obs::stringliteral_from_digit(1).value_, "1") == 0); + //REQUIRE(strcmp(obs::ratio2str>().value_, "") == 0); + + } /*TEST_CASE(native_dim_abbrev)*/ + + TEST_CASE("dimension", "[dimension]") { + constexpr bool c_debug_flag = true; + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.dimension")); + //log && log("(A)", xtag("foo", foo)); + + using t1 = obs::bpu>; + + static_assert(t1::c_native_dim == obs::dim::currency); + static_assert(t1::power_type::num == 1); + static_assert(t1::power_type::den == 1); + + using t2 = obs::bpu, std::ratio<-1,2>>; + + static_assert(t2::c_native_dim == obs::dim::time); + static_assert(t2::power_type::num == -1); + static_assert(t2::power_type::den == 2); + + using dim1 = wrap_unit, bpu_node>; + using d1 = dim1::dim_type; /* ccy */ + REQUIRE(unused_same()); + REQUIRE(unused_same::power_unit_type, t1>()); +#ifdef NOT_USING + static_assert(obs::lo_basis_elt_of::c_lo_basis == t1::c_basis); +#endif + + static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 0); + static_assert(obs::native_lo_bwp_of::bwp_type::c_basis == obs::dim::currency); + + + using dim2 = wrap_unit, bpu_node>; + using d2 = dim2::dim_type; /* t^(-1/2) */ + REQUIRE(unused_same()); + REQUIRE(unused_same::power_unit_type, t2>()); + static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 0); + static_assert(obs::native_lo_bwp_of::bwp_type::c_basis == obs::dim::time); + + using dim3 = wrap_unit, bpu_node>>; + using d3 = dim3::dim_type; /* ccy.t^(-1/2) */ + REQUIRE(unused_same::power_unit_type, t1>()); + + { + using type = obs::lookup_bpu::power_unit_type; + //std::cerr << "obs::power_unit_of::power_unit_type" << xtag("type", reflect::type_name()) << std::endl; + + REQUIRE(unused_same()); + } + +#ifdef NOT_USING + static_assert(obs::lo_basis_elt_of::c_lo_basis == t2::c_basis); +#endif + + /* lowest is in pos 1, beacuse t2=time before t1=currency */ + static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 1); + + static_assert(unused_same::dim_type, d2>()); + //using type = obs::without_elt::dim_type; + //std::cerr << "obs::without_elt::dim_type" << xtag("type", reflect::type_name()) << std::endl; + static_assert(unused_same::dim_type, d1>()); + + + using d3b = wrap_unit, + bpu_node>>::dim_type; /* t^(-1/2).ccy */ + //using d3b = obs::dimension_impl>; /* t^(-1/2).ccy */ + REQUIRE(unused_same::power_unit_type, t2>()); + REQUIRE(unused_same::power_unit_type, t1>()); + + /* lowest is in pos 0 */ + static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 0); + + static_assert(unused_same::dim_type, d1>()); + static_assert(unused_same::dim_type, d2>()); + + static_assert(unused_same, obs::canonical_t>()); + + log && log(xtag("d1.abbrev", unit_abbrev_v.c_str())); + log && log(xtag("d2.abbrev", unit_abbrev_v.c_str())); + log && log(xtag("d3.abbrev", unit_abbrev_v.c_str())); + } + + TEST_CASE("dimension2", "[dimension2]") { + constexpr bool c_debug_flag = true; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.dimension2")); + //log && log("(A)", xtag("foo", foo)); + + using di = di_cartesian_product; + + log && log(xtag("di", Reflect::require()->canonical_name())); + log && log(xtag("di::outer_scalefactor_type", Reflect::require()->canonical_name())); + log && log(xtag("di::bpu_list_type", Reflect::require()->canonical_name())); + + using u1 = unit_cartesian_product_t; + + log && log(xtag("u1", Reflect::require()->canonical_name())); + + log && log(xtag("u1", ccs(unit_abbrev_v.value_))); + } /*TEST_CASE(dimension2)*/ + + TEST_CASE("dimension3", "[dimension3]") { + constexpr bool c_debug_flag = true; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.dimension3")); + //log && log("(A)", xtag("foo", foo)); + + using u1 = unit_invert_t; + + log && log(xtag("second^-1", Reflect::require()->canonical_name())); + log && log(xtag("u1", unit_abbrev_v.c_str())); + + REQUIRE(strcmp(unit_abbrev_v.c_str(), "s^-1") == 0); + + using u2 = second; + + log && log(xtag("second", Reflect::require()->canonical_name())); + log && log(xtag("u2", unit_abbrev_v.c_str())); + + using u1u2 = unit_cartesian_product_t; + + log && log(xtag("u1u2", Reflect::require()->canonical_name())); + +#ifdef NOT_USING + using di1 = d1::dim_type; + using di2 = d2::dim_type; + using di1di2 = di_cartesian_product::type; + + log && log(xtag("di1di2", Reflect::require()->canonical_name())); +#endif + + using f1 = u1::dim_type::front_type; + using r1 = u1::dim_type::rest_type; + using tmp = di_cartesian_product1; + + log && log(xtag("f1", Reflect::require()->canonical_name())); + log && log(xtag("r1", Reflect::require()->canonical_name())); + log && log(xtag("(f1.r1).outer_scalefactor_type", Reflect::require()->canonical_name())); + log && log(xtag("(f1.r1).bpu_list_type", Reflect::require()->canonical_name())); + + using tmp2 = bpu_cartesian_product; + + log && log(xtag("(f1.u2).outer_scalefactor_type", Reflect::require()->canonical_name())); + log && log(xtag("(f1.u2).bpu_list_type", Reflect::require()->canonical_name())); + + using f2 = u2::dim_type::front_type; + log && log(xtag("f2", Reflect::require()->canonical_name())); + + using tmp3 = bpu_cartesian_product_helper; + log && log(xtag("(f1.f2).outer_scalefactor_type", Reflect::require()->canonical_name())); + log && log(xtag("(f1.f2).bpu_list_type", Reflect::require()->canonical_name())); + } /*TEST_CASE(dimension3)*/ + + + } /*namespace ut*/ + +} /*namespace xo*/ + + +/* end dimension.test.cpp */ diff --git a/utest/unit_utest_main.cpp b/utest/unit_utest_main.cpp new file mode 100644 index 00000000..81f2ade0 --- /dev/null +++ b/utest/unit_utest_main.cpp @@ -0,0 +1,6 @@ +/* file unit_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end unit_utest_main.cpp */ From b480e4a0e657bf22a7af219414d778640e243c96 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 19:43:30 -0400 Subject: [PATCH 0544/2524] doc: + README.md --- README.md | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d0133c7d..68174012 100644 --- a/README.md +++ b/README.md @@ -1 +1,38 @@ -# xo-unit +# unit library + +Provides compile-time dimension checking and scaling. + +Similar to `boost::units`, but: +1. streamlined: assumes modern (c++20) support +2. supports fractional dimensions (rational powers) + +## Getting Started + +### build + install dependencies + +- [github/Rconybea/reflect](https://github.com/Rconybea/reflect) + +### build + install +``` +$ cd xo-unit +$ mkdir .build +$ cd .build +$ PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} .. +$ make +$ make install +``` + +### build for unit test coverage +``` +$ cd xo-unit +$ mkdir .build-ccov +$ cd .build-ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +### LSP support +``` +$ cd xo-unit +$ ln -s .build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` From b8f2360b899739b72272036324a6997aa9169c8f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 19:47:48 -0400 Subject: [PATCH 0545/2524] .gitignore: + compile_commands.json --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 24e5b0a1..7b6765fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .build +compile_commands.json From 83810d6c0048424a527e93484bf328d5deb0ce89 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 19:49:51 -0400 Subject: [PATCH 0546/2524] build: comments in CMakeLists.txt --- src/kalmanfilter/CMakeLists.txt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/kalmanfilter/CMakeLists.txt b/src/kalmanfilter/CMakeLists.txt index b7036526..2dbdcff0 100644 --- a/src/kalmanfilter/CMakeLists.txt +++ b/src/kalmanfilter/CMakeLists.txt @@ -21,11 +21,10 @@ set(SELF_SRCS xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) # ---------------------------------------------------------------- -# internal dependencies - +# Dependencies +# +# REMINDER: these must coodinate with find_dependency() calls in +# [xo-kalmanfilter/cmake/xo_kalmanfilterConfig.cmake.in] +# xo_dependency(${SELF_LIB} reactor) - -# ---------------------------------------------------------------- -# external dependencies - xo_external_target_dependency(${SELF_LIB} Eigen3 Eigen3::Eigen) From 35c4fbdca8c86a9b262bed9298af5596b2786a4d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 19:55:20 -0400 Subject: [PATCH 0547/2524] build: suppress cmake messages in standalone build --- cmake/xo-bootstrap-macros.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 16644435..96592216 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -3,8 +3,10 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) endif() -message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") -message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +if (NOT XO_SUBMODULE_BUILD) + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) From ad8c028e73cad88dcec03c5b4eef74c89e40b015 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 20:11:11 -0400 Subject: [PATCH 0548/2524] + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..13c0afb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json From d6342f7269274bfeaa71bd81ad741cdac906608e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 20:11:20 -0400 Subject: [PATCH 0549/2524] github: + ubuntu base CI workflow --- .github/workflows/ubuntu-main.yml | 239 ++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 .github/workflows/ubuntu-main.yml diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml new file mode 100644 index 00000000..0acda62e --- /dev/null +++ b/.github/workflows/ubuntu-main.yml @@ -0,0 +1,239 @@ +name: build xo-unit + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + echo "::group::install catch2" + # install catch2. see + # [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + sudo apt-get install -y catch2 + echo "::endgroup" + + #echo "::group::install pybind11" + #sudo apt-get install -y pybind11-dev + #echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: build xo-cmake + run: | + XONAME=xo-cmake + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/xo-indentlog + + - name: build xo-indentlog + run: | + XONAME=xo-indentlog + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/xo-refcnt + + - name: build xo-refcnt + run: | + XONAME=xo-refcnt + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/xo-subsys + + - name: build xo-subsys + run: | + XONAME=xo-subsys + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/xo-reflect + + - name: build xo-reflect + run: | + XONAME=xo-reflect + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: build self (xo-unit) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: | + XONAME=xo-unit + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + (cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}}) From 2e1214efbe1dd003bfbb3dc32936dfbd09adb0b6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 20:13:51 -0400 Subject: [PATCH 0550/2524] github: + doxygen dep --- .github/workflows/ubuntu-main.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml index 0acda62e..11cd66e5 100644 --- a/.github/workflows/ubuntu-main.yml +++ b/.github/workflows/ubuntu-main.yml @@ -23,12 +23,17 @@ jobs: - name: Install dependencies run: | - echo "::group::install catch2" - # install catch2. see + # install catch2, doxygen. see # [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + + echo "::group::install catch2" sudo apt-get install -y catch2 echo "::endgroup" + echo "::group::install doxygen" + sudo apt-get install -y doxygen + echo "::endgroup" + #echo "::group::install pybind11" #sudo apt-get install -y pybind11-dev #echo "::endgroup" From 06a7e938e452bd300aac7a45dfccd85a49c86e19 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Apr 2024 20:16:46 -0400 Subject: [PATCH 0551/2524] build: drop stray #include (did you do that, lsp?) --- include/xo/unit/dimension_impl.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/unit/dimension_impl.hpp b/include/xo/unit/dimension_impl.hpp index 49262655..7a8e7aff 100644 --- a/include/xo/unit/dimension_impl.hpp +++ b/include/xo/unit/dimension_impl.hpp @@ -6,7 +6,7 @@ #include "native_bpu.hpp" #include "dim_util.hpp" #include "ratio_util.hpp" -#include +//#include #include #include #include From 7e61533cafc32c98066da786beef779e5e54e28d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Apr 2024 17:28:15 -0400 Subject: [PATCH 0552/2524] xo-unit: move types: xo::obs:: -> xo::unit:: --- include/xo/unit/basis_unit.hpp | 8 +- include/xo/unit/dim_util.hpp | 6 +- include/xo/unit/dimension_concept.hpp | 5 +- include/xo/unit/dimension_impl.hpp | 4 +- include/xo/unit/native_bpu.hpp | 4 +- include/xo/unit/native_bpu_concept.hpp | 5 +- include/xo/unit/quantity.hpp | 8 +- include/xo/unit/quantity_concept.hpp | 4 +- include/xo/unit/ratio_concept.hpp | 4 +- include/xo/unit/ratio_util.hpp | 4 +- include/xo/unit/stringliteral.hpp | 4 +- include/xo/unit/unit.hpp | 19 +++-- utest/quantity.test.cpp | 76 +++++++++++++----- utest/unit.test.cpp | 104 ++++++++++++------------- 14 files changed, 144 insertions(+), 111 deletions(-) diff --git a/include/xo/unit/basis_unit.hpp b/include/xo/unit/basis_unit.hpp index cd0b2a02..37281553 100644 --- a/include/xo/unit/basis_unit.hpp +++ b/include/xo/unit/basis_unit.hpp @@ -4,7 +4,7 @@ #include "ratio_util.hpp" namespace xo { - namespace obs { + namespace unit { /** @class basis_unit * * @brief A dimensionless multiple with natively-specified (i.e. at compile-time) dimension @@ -56,9 +56,9 @@ namespace xo { template constexpr auto native_unit_abbrev_v = native_unit_abbrev_helper::value; - // ----- scaled_native_unit_abbrev_helper ----- - namespace units { + // ----- scaled_native_unit_abbrev_helper ----- + /* Require: InnerScale is ratio type; InnerScale >= 1 */ template struct scaled_native_unit_abbrev; @@ -80,7 +80,7 @@ namespace xo { template constexpr auto scaled_native_unit_abbrev_v = scaled_native_unit_abbrev::value; } - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ /** end basis_unit.hpp **/ diff --git a/include/xo/unit/dim_util.hpp b/include/xo/unit/dim_util.hpp index 222f9c9e..ad2f3496 100644 --- a/include/xo/unit/dim_util.hpp +++ b/include/xo/unit/dim_util.hpp @@ -2,10 +2,11 @@ #pragma once + #include "stringliteral.hpp" namespace xo { - namespace obs { + namespace unit { enum class dim { /** weight. native unit = 1 gram **/ mass, @@ -52,8 +53,7 @@ namespace xo { template constexpr auto native_unit_for_v = native_unit_for::value; - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ - /* end dim_util.hpp */ diff --git a/include/xo/unit/dimension_concept.hpp b/include/xo/unit/dimension_concept.hpp index c1514e40..6277b621 100644 --- a/include/xo/unit/dimension_concept.hpp +++ b/include/xo/unit/dimension_concept.hpp @@ -3,10 +3,9 @@ #pragma once #include "native_bpu_concept.hpp" -//#include namespace xo { - namespace obs { + namespace unit { /** checks most non-empty BPU (basis power unit) node types; * cannot check BpuList::rest_type, because concept definition * can't (as of c++23) be recursive. @@ -83,7 +82,7 @@ namespace xo { && (ratio_concept && bpu_list_concept && bpu_list_concept); - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ /* end dimension_concept.hpp */ diff --git a/include/xo/unit/dimension_impl.hpp b/include/xo/unit/dimension_impl.hpp index 7a8e7aff..d51c655c 100644 --- a/include/xo/unit/dimension_impl.hpp +++ b/include/xo/unit/dimension_impl.hpp @@ -17,7 +17,7 @@ namespace xo { * - bpu_list -> bpu_node */ - namespace obs { + namespace unit { // ----- lookup_bpu ----- /** @@ -510,7 +510,7 @@ namespace xo { template using canonical_t = canonical_impl::dim_type; - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ /* end dimension_impl.hpp */ diff --git a/include/xo/unit/native_bpu.hpp b/include/xo/unit/native_bpu.hpp index a3e716a0..44e1a54d 100644 --- a/include/xo/unit/native_bpu.hpp +++ b/include/xo/unit/native_bpu.hpp @@ -7,7 +7,7 @@ #include namespace xo { - namespace obs { + namespace unit { // ----- native_bpu ----- /** @class native_bpu @@ -253,7 +253,7 @@ namespace xo { _p_type /*Power*/ >; }; - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ /* end native_bpu.hpp */ diff --git a/include/xo/unit/native_bpu_concept.hpp b/include/xo/unit/native_bpu_concept.hpp index 776e18bf..f72c60ca 100644 --- a/include/xo/unit/native_bpu_concept.hpp +++ b/include/xo/unit/native_bpu_concept.hpp @@ -7,7 +7,7 @@ #include namespace xo { - namespace obs { + namespace unit { /** * e.g. see native_bpu> * @@ -35,8 +35,7 @@ namespace xo { && (std::is_signed_v)) // && std::copyable ; - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ - /* end native_bpu_concept.hpp */ diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 286a9e16..adb6cf80 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -8,7 +8,7 @@ //#include "xo/indentlog/scope.hpp" namespace xo { - namespace obs { + namespace unit { /** @class promoter * * Aux class assister for quantity::promote() @@ -35,7 +35,7 @@ namespace xo { * - Repr * Repr -> Repr * - Repr / Repr -> Repr **/ - template //, typename RequiredDimensionType = Unit> + template class quantity { public: using unit_type = Unit; @@ -415,7 +415,7 @@ namespace xo { }; template - inline auto kilograms(Repr x) -> quantity { + inline auto kilograms(Repr x) -> quantity { return quantity::promote(x); }; @@ -495,7 +495,7 @@ namespace xo { return quantity::promote(x); } } /*namespace qty*/ - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ /* end quantity.hpp */ diff --git a/include/xo/unit/quantity_concept.hpp b/include/xo/unit/quantity_concept.hpp index 963a0246..09d33446 100644 --- a/include/xo/unit/quantity_concept.hpp +++ b/include/xo/unit/quantity_concept.hpp @@ -5,7 +5,7 @@ #include namespace xo { - namespace obs { + namespace unit { template concept quantity_concept = requires(Quantity qty, typename Quantity::repr_type repr) { @@ -17,5 +17,5 @@ namespace xo { { Quantity::unit_quantity() } -> std::same_as; { Quantity::promote(repr) } -> std::same_as; }; - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ diff --git a/include/xo/unit/ratio_concept.hpp b/include/xo/unit/ratio_concept.hpp index 3308b1f1..ecfeb4bf 100644 --- a/include/xo/unit/ratio_concept.hpp +++ b/include/xo/unit/ratio_concept.hpp @@ -5,9 +5,9 @@ #include namespace xo { - namespace obs { + namespace unit { template concept ratio_concept = (std::is_signed_v && std::is_signed_v); - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ diff --git a/include/xo/unit/ratio_util.hpp b/include/xo/unit/ratio_util.hpp index 7695db3d..1d7e3199 100644 --- a/include/xo/unit/ratio_util.hpp +++ b/include/xo/unit/ratio_util.hpp @@ -8,7 +8,7 @@ #include namespace xo { - namespace obs { + namespace unit { // ----- ratio_floor ----- template @@ -236,7 +236,7 @@ namespace xo { return exponent2str_aux().value; }; - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ /* end ratio_util.hpp */ diff --git a/include/xo/unit/stringliteral.hpp b/include/xo/unit/stringliteral.hpp index c6bc6243..b3e68fe8 100644 --- a/include/xo/unit/stringliteral.hpp +++ b/include/xo/unit/stringliteral.hpp @@ -8,7 +8,7 @@ #include namespace xo { - namespace obs { + namespace unit { // ----- stringliteral ----- @@ -164,7 +164,7 @@ namespace xo { static constexpr auto value = stringliteral_concat("-", _suffix.value_); }; - } /*namespace obs*/ + } /*namespace unit*/ } /*namespace xo*/ /* end stringliteral.hpp */ diff --git a/include/xo/unit/unit.hpp b/include/xo/unit/unit.hpp index d081077e..b8b8c1b9 100644 --- a/include/xo/unit/unit.hpp +++ b/include/xo/unit/unit.hpp @@ -1,11 +1,11 @@ -/* @file dimension.hpp */ +/* @file unit.hpp */ #pragma once #include "dimension_impl.hpp" namespace xo { - namespace obs { + namespace unit { // ----- wrap_unit ----- template @@ -26,6 +26,7 @@ namespace xo { // ----- normalize_unit ----- + /* like Unit, but with scalefactor 1 */ template struct normalize_unit { static_assert(unit_concept); @@ -239,7 +240,7 @@ namespace xo { using milligram = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; + std::ratio<1, 1000>> > >; template <> struct scaled_native_unit_abbrev> { @@ -248,10 +249,10 @@ namespace xo { using gram = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; + std::ratio<1>> > >; using kilogram = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; + std::ratio<1000>> > >; template <> struct scaled_native_unit_abbrev> { @@ -385,10 +386,8 @@ namespace xo { using price = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; - } - - } /*namespace obs*/ + } /*namespace units*/ + } /*namespace unit*/ } /*namespace xo*/ - -/* end dimension.hpp */ +/* end unit.hpp */ diff --git a/utest/quantity.test.cpp b/utest/quantity.test.cpp index 8274012c..352ca2c5 100644 --- a/utest/quantity.test.cpp +++ b/utest/quantity.test.cpp @@ -9,31 +9,32 @@ #include namespace xo { - using xo::obs::quantity; + using xo::unit::quantity; - using xo::obs::qty::milliseconds; - using xo::obs::qty::seconds; - using xo::obs::qty::minutes; - using xo::obs::qty::volatility30d; - using xo::obs::qty::volatility250d; + using xo::unit::qty::kilograms; + using xo::unit::qty::milliseconds; + using xo::unit::qty::seconds; + using xo::unit::qty::minutes; + using xo::unit::qty::volatility30d; + using xo::unit::qty::volatility250d; - using xo::obs::unit_find_bpu_t; - using xo::obs::unit_conversion_factor_t; - using xo::obs::unit_cartesian_product_t; - using xo::obs::unit_cartesian_product; - using xo::obs::unit_invert_t; - using xo::obs::unit_abbrev_v; - using xo::obs::same_dimension_v; - using xo::obs::dim; + using xo::unit::unit_find_bpu_t; + using xo::unit::unit_conversion_factor_t; + using xo::unit::unit_cartesian_product_t; + using xo::unit::unit_cartesian_product; + using xo::unit::unit_invert_t; + using xo::unit::unit_abbrev_v; + using xo::unit::same_dimension_v; + using xo::unit::dim; - using xo::obs::from_ratio; - using xo::obs::stringliteral_from_ratio; - using xo::obs::ratio2str_aux; - using xo::obs::cstr_from_ratio; + using xo::unit::from_ratio; + using xo::unit::stringliteral_from_ratio; + using xo::unit::ratio2str_aux; + using xo::unit::cstr_from_ratio; using xo::reflect::Reflect; - namespace units = xo::obs::units; + namespace units = xo::unit::units; namespace ut { /* use 'testcase' snippet to add test cases here */ @@ -626,7 +627,7 @@ namespace xo { //auto rng = xo::rng::xoshiro256ss(seed); - scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.mult2")); + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div4")); //log && log("(A)", xtag("foo", foo)); auto q1 = volatility250d(0.2); @@ -647,6 +648,41 @@ namespace xo { CHECK(r == Approx(0.692820323).epsilon(1e-6)); } /*TEST_CASE(div4)*/ + + TEST_CASE("muldiv5", "[quantity]") { + constexpr bool c_debug_flag = true; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.muldiv5")); + //log && log("(A)", xtag("foo", foo)); + + auto t = milliseconds(10); + auto m = kilograms(2.5); + + auto a = m / (t * t); + + /* 0.1/sqrt(30dy) ~ 0.288675/sqrt(250dy), + * so q1/q2 ~ 0.6928 + */ + + log && log(XTAG(m), XTAG(t), xtag("a=m.t^-2", a)); + + /* verify dimensions of result + sticky units */ + CHECK(strcmp(t.unit_cstr(), "ms") == 0); + CHECK(strcmp(m.unit_cstr(), "kg") == 0); + CHECK(strcmp(a.unit_cstr(), "kg.ms^-2") == 0); + + CHECK(a.scale() == 0.025); + + /* verify scale of result */ + //CHECK(r == Approx(0.692820323).epsilon(1e-6)); + + } /*TEST_CASE(div4)*/ } /*namespace ut*/ } /*namespace xo*/ diff --git a/utest/unit.test.cpp b/utest/unit.test.cpp index bccc5d47..d3ea40bf 100644 --- a/utest/unit.test.cpp +++ b/utest/unit.test.cpp @@ -14,33 +14,33 @@ namespace xo { using xo::reflect::Reflect; - using xo::obs::dim; - using xo::obs::native_unit_abbrev_v; - using xo::obs::units::scaled_native_unit_abbrev_v; - //using xo::obs::native_dim_abbrev; - using xo::obs::stringliteral_compare; - using xo::obs::literal_size_v; - using xo::obs::stringliteral_from_digit; - using xo::obs::stringliteral_from_int_v; - using xo::obs::stringliteral; + using xo::unit::dim; + using xo::unit::native_unit_abbrev_v; + using xo::unit::units::scaled_native_unit_abbrev_v; + //using xo::unit::native_dim_abbrev; + using xo::unit::stringliteral_compare; + using xo::unit::literal_size_v; + using xo::unit::stringliteral_from_digit; + using xo::unit::stringliteral_from_int_v; + using xo::unit::stringliteral; #ifndef __clang__ - using xo::obs::stringliteral_concat; - using xo::obs::stringliteral_from_ratio; - using xo::obs::bpu_assemble_abbrev_helper; - using xo::obs::bpu_assemble_abbrev; + using xo::unit::stringliteral_concat; + using xo::unit::stringliteral_from_ratio; + using xo::unit::bpu_assemble_abbrev_helper; + using xo::unit::bpu_assemble_abbrev; #endif - using xo::obs::bpu_node; - using xo::obs::wrap_unit; - using xo::obs::unit_abbrev_v; - //using xo::obs::dim_abbrev_v; - using xo::obs::di_cartesian_product; - using xo::obs::di_cartesian_product1; - using xo::obs::unit_cartesian_product_t; - using xo::obs::bpu_cartesian_product; - using xo::obs::bpu_cartesian_product_helper; - using xo::obs::unit_invert_t; - using xo::obs::units::gram; - using xo::obs::units::second; + using xo::unit::bpu_node; + using xo::unit::wrap_unit; + using xo::unit::unit_abbrev_v; + //using xo::unit::dim_abbrev_v; + using xo::unit::di_cartesian_product; + using xo::unit::di_cartesian_product1; + using xo::unit::unit_cartesian_product_t; + using xo::unit::bpu_cartesian_product; + using xo::unit::bpu_cartesian_product_helper; + using xo::unit::unit_invert_t; + using xo::unit::units::gram; + using xo::unit::units::second; using xo::print::ccs; template @@ -180,74 +180,74 @@ namespace xo { scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.dimension")); //log && log("(A)", xtag("foo", foo)); - using t1 = obs::bpu>; + using t1 = unit::bpu>; - static_assert(t1::c_native_dim == obs::dim::currency); + static_assert(t1::c_native_dim == unit::dim::currency); static_assert(t1::power_type::num == 1); static_assert(t1::power_type::den == 1); - using t2 = obs::bpu, std::ratio<-1,2>>; + using t2 = unit::bpu, std::ratio<-1,2>>; - static_assert(t2::c_native_dim == obs::dim::time); + static_assert(t2::c_native_dim == unit::dim::time); static_assert(t2::power_type::num == -1); static_assert(t2::power_type::den == 2); using dim1 = wrap_unit, bpu_node>; using d1 = dim1::dim_type; /* ccy */ REQUIRE(unused_same()); - REQUIRE(unused_same::power_unit_type, t1>()); + REQUIRE(unused_same::power_unit_type, t1>()); #ifdef NOT_USING - static_assert(obs::lo_basis_elt_of::c_lo_basis == t1::c_basis); + static_assert(unit::lo_basis_elt_of::c_lo_basis == t1::c_basis); #endif - static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 0); - static_assert(obs::native_lo_bwp_of::bwp_type::c_basis == obs::dim::currency); + static_assert(unit::native_lo_bwp_of::bwp_type::c_index == 0); + static_assert(unit::native_lo_bwp_of::bwp_type::c_basis == unit::dim::currency); using dim2 = wrap_unit, bpu_node>; using d2 = dim2::dim_type; /* t^(-1/2) */ REQUIRE(unused_same()); - REQUIRE(unused_same::power_unit_type, t2>()); - static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 0); - static_assert(obs::native_lo_bwp_of::bwp_type::c_basis == obs::dim::time); + REQUIRE(unused_same::power_unit_type, t2>()); + static_assert(unit::native_lo_bwp_of::bwp_type::c_index == 0); + static_assert(unit::native_lo_bwp_of::bwp_type::c_basis == unit::dim::time); using dim3 = wrap_unit, bpu_node>>; using d3 = dim3::dim_type; /* ccy.t^(-1/2) */ - REQUIRE(unused_same::power_unit_type, t1>()); + REQUIRE(unused_same::power_unit_type, t1>()); { - using type = obs::lookup_bpu::power_unit_type; - //std::cerr << "obs::power_unit_of::power_unit_type" << xtag("type", reflect::type_name()) << std::endl; + using type = unit::lookup_bpu::power_unit_type; + //std::cerr << "unit::power_unit_of::power_unit_type" << xtag("type", reflect::type_name()) << std::endl; REQUIRE(unused_same()); } #ifdef NOT_USING - static_assert(obs::lo_basis_elt_of::c_lo_basis == t2::c_basis); + static_assert(unit::lo_basis_elt_of::c_lo_basis == t2::c_basis); #endif /* lowest is in pos 1, beacuse t2=time before t1=currency */ - static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 1); + static_assert(unit::native_lo_bwp_of::bwp_type::c_index == 1); - static_assert(unused_same::dim_type, d2>()); - //using type = obs::without_elt::dim_type; - //std::cerr << "obs::without_elt::dim_type" << xtag("type", reflect::type_name()) << std::endl; - static_assert(unused_same::dim_type, d1>()); + static_assert(unused_same::dim_type, d2>()); + //using type = unit::without_elt::dim_type; + //std::cerr << "unit::without_elt::dim_type" << xtag("type", reflect::type_name()) << std::endl; + static_assert(unused_same::dim_type, d1>()); using d3b = wrap_unit, bpu_node>>::dim_type; /* t^(-1/2).ccy */ - //using d3b = obs::dimension_impl>; /* t^(-1/2).ccy */ - REQUIRE(unused_same::power_unit_type, t2>()); - REQUIRE(unused_same::power_unit_type, t1>()); + //using d3b = unit::dimension_impl>; /* t^(-1/2).ccy */ + REQUIRE(unused_same::power_unit_type, t2>()); + REQUIRE(unused_same::power_unit_type, t1>()); /* lowest is in pos 0 */ - static_assert(obs::native_lo_bwp_of::bwp_type::c_index == 0); + static_assert(unit::native_lo_bwp_of::bwp_type::c_index == 0); - static_assert(unused_same::dim_type, d1>()); - static_assert(unused_same::dim_type, d2>()); + static_assert(unused_same::dim_type, d1>()); + static_assert(unused_same::dim_type, d2>()); - static_assert(unused_same, obs::canonical_t>()); + static_assert(unused_same, unit::canonical_t>()); log && log(xtag("d1.abbrev", unit_abbrev_v.c_str())); log && log(xtag("d2.abbrev", unit_abbrev_v.c_str())); From d9868870e4790b414493495c81410c7d936ee4d2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Apr 2024 17:29:10 -0400 Subject: [PATCH 0553/2524] xo-unit: docs: + sphinx + breathe + expand docs --- docs/CMakeLists.txt | 83 ++++++++++++++++++++++--------- docs/examples.rst | 117 ++++++++++++++++++++++++++++++++++++++++++++ docs/glossary.rst | 26 ++++++++++ docs/index.rst | 40 +++++++++++++++ docs/install.rst | 62 +++++++++++++++++++++++ 5 files changed, 305 insertions(+), 23 deletions(-) create mode 100644 docs/examples.rst create mode 100644 docs/glossary.rst create mode 100644 docs/index.rst create mode 100644 docs/install.rst diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 99a1fdcc..bc00ca12 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -1,34 +1,41 @@ # xo-unit/docs/CMakeLists.txt -# copied from xo-observable/docs/CMakeLists.txt - -set(DOX_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) - -#set(DOX_DEPS ${PROJECT_SOURCE_DIR}/mainpage.dox) # not yet -set(DOX_INPUT_DIR ${PROJECT_SOURCE_DIR}) -set(DOX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dox) - -set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html) - -#set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/sphinx/html) -#set(SPHINX_INDEX_FILE ${SPHINX_OUTPUT_DIR}/index.html) -# -## sphinx .rst files reachable from cmake-examples/docs -#file(GLOB_RECURSE SPHINX_RST_FILES ${CMAKE_CURRENT_SOURCE_DIR} *.rst) - -set(ALL_LIBRARY_TARGETS xo_unit) # todo: automate this from xo-cmake macros -#set(ALL_UTEST_TARGETS "") # todo: automate this from xo-cmake macros - -# look for doxygen executable -find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED) -message("-- DOXYGEN_EXECUTABLE=${DOXYGEN_EXECUTABLE}") - if (XO_SUBMODULE_BUILD) # in submodule build, rely on toplevel docs/CMakeLists.txt file instead else() # build docs starting from here only in standalone build. # otherwise use top-level doxygen setup instead. + set(ALL_LIBRARY_TARGETS xo_unit) # todo: automate this from xo-cmake macros + set(ALL_UTEST_TARGETS utest.unit) # todo: automate this from xo-cmake macros + + # look for doxygen executable + find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED) + message("-- DOXYGEN_EXECUTABLE=${DOXYGEN_EXECUTABLE}") + + # look for sphinx-build executable + find_program(SPHINX_EXECUTABLE NAMES sphinx-build REQUIRED) + message("-- SPHINX_EXECUTABLE=${SPHINX_EXECUTABLE}") + + set(DOX_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + + #set(DOX_DEPS ${PROJECT_SOURCE_DIR}/mainpage.dox) # not yet + set(DOX_INPUT_DIR ${PROJECT_SOURCE_DIR}) + set(DOX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dox) + + set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html) + + set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/sphinx/html) + set(SPHINX_INDEX_FILE ${SPHINX_OUTPUT_DIR}/index.html) + # + # sphinx .rst files reachable from cmake-examples/docs + # + # REMINDER: will need to re-run cmake when the set of .rst files changes + # + file(GLOB_RECURSE SPHINX_RST_FILES_GLOB ${CMAKE_CURRENT_SOURCE_DIR} *.rst) + + set(SPHINX_RST_FILES index.rst install.rst examples.rst classes.rst glossary.rst) + # TODO: # 1. move Doxyfile.in to xo-cmake project # 2. replace this command section with xo-cmake macro @@ -57,4 +64,34 @@ else() doxygen DEPENDS ${DOX_INDEX_FILE} ) + + # root of sphinx doc tree + set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) + + add_custom_command( + OUTPUT ${SPHINX_INDEX_FILE} + DEPENDS doxygen conf.py ${SPHINX_RST_FILES} ${SPHINX_RST_FILES_GLOB} + COMMAND ${SPHINX_EXECUTABLE} + -b html -Dbreathe_projects.xodoxxml=${CMAKE_CURRENT_BINARY_DIR}/dox/xml + ${SPHINX_SOURCE} ${SPHINX_OUTPUT_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating docs (sphinx) -> [${SPHINX_OUTPUT_DIR}]") + + # make sphinx --> generate sphinx documentation + # + add_custom_target( + sphinx + DEPENDS ${SPHINX_INDEX_FILE}) + + # - html docs generated in build/docs/sphinx + # - copy the doc tree to share/doc/xo_unit/html + # + # OPTIONAL: install directory tree if it exists, + # but don't complain if it's missing + install( + DIRECTORY ${SPHINX_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME} + COMPONENT Documentation + OPTIONAL) endif() diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 00000000..1aba11ed --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,117 @@ +.. _examples: + +.. toctree + :maxdepth: 2 + +Examples +======== + +Compile-time unit inference +--------------------------- + +.. code-block:: cpp + + #include "xo/unit/quantity.hpp" + #include + + int main() { + namespace u = xo::unit; + namespace qty = u::qty; + using namespace std; + + auto t = qty::milliseconds(10); + auto m = qty::kilograms(2.5); + auto a = m / (t * t); + + static_assert(same_as>); + static_assert(same_as>); + static_assert(sizeof(t) == sizeof(int)); + static_assert(sizeof(m) == sizeof(double)); + static_assert(sizeof(a) == sizeof(double)); + + cerr << "t: " << t << ", m: " << m << ", a: " << a << endl; + } + +with output: + +.. code-block:: + + t: 10ms, m: 2.5kg, m.t^-2: 0.025kg.ms^-2 + +Remarks: + +* The ``xo-unit`` system runs entirely at compile time; there's no runtime overhead. +* No runtime overhead includes construction of literal strings such as ``kg.ms^-2`` + (this is once place implementation requires c++20) +* Units are sticky: since we expressed ``t`` in milliseconds and ``m`` in kilograms, result is in the same terms. +* Unit ordering is sticky. Mass appears on the left in printed value of ``a`` because it was on the left-hand side of ``operator/`` +* Example omits verifying ``decltype(a)``, to keep output small. + +Scale conversion triggered by assignment +---------------------------------------- + +One way to convert units is by assignment: + +.. code-block:: cpp + :linenos: + :emphasize-lines: 9-10 + + #include "xo/unit/quantity.hpp" + #include + + int main() { + namespace u = xo::unit; + namespace qty = xo::units::qty; + using namespace std; + + quantity t = qty::milliseconds(10); + quantity m = qty::kilograms(2.5); + auto a = m / (t * t); + + cerr << "t: " << t << ", m: " << m << ", a: " << a << endl; + } + +with output: + +.. code-block:: + + t: 0.01s, m: 2500g, m.t^-2: 2.5e+07g.s^-2 + +Remarks: + +* Assignment to ``t`` converted to representation ``double``. + We could have used :code:`quantity` to convert (possibly rounding down) + representation to `int`. + +Scale conversion triggered by arithmetic +---------------------------------------- + +In representing a particular quantity, +xo-unit uses at most one scale for each :term:`basis dimension` associated with the unit for that quantity. +When an arithmetic operator encounters basis units involving two different scales, +the operator will adopt the scale provided by the left-hand argument: + +.. code-block:: cpp + :linenos: + :emphasize-lines: 11 + + #include "xo/unit/quantity.hpp" + #include + + int main() { + namespace u = xo::unit; + namespace qty = xo::units::qty; + using namespace std; + + auto t1 = qty::milliseconds(1); + auto t2 = qty::minutes(1); + auto p = t1 * t2; + + cerr << "t1: " << t1 << ", t2: " << t2 << ", p: " << p << endl; + } + +with output: + +.. code-block:: + + t1: 1ms, t2: 1min, t1*t2: 60000ms^2 diff --git a/docs/glossary.rst b/docs/glossary.rst new file mode 100644 index 00000000..5d594f81 --- /dev/null +++ b/docs/glossary.rst @@ -0,0 +1,26 @@ +.. _glossary: + +Glossary +-------- + +.. glossary:: + basis dimension + Orthogonal directions associated with basis units, for example *mass*, *length*, *time*. + In xo-unit these are represented by the enum ``xo::unit::dim``. + + basis unit + An implementation type representing a quantity (with associated scale) in the direction of a single :term:`basis dimension`. + For example *milliseconds*, *seconds*, and *hours* stand for different basis units with the *time* dimension. + In xo-unit these are represented by the template type ``xo::unit::basis_unit``. + + bpu + A rational power of a (single) basis unit. For example *kg.m.s\ :sup:-2* or *hr\ :sup:-(1/2)*. + In xo-unit these are represented by the template type ``xo::unit::bpu``. + + XO + A set of integrated c++ libraries for complex event processing, with browser and python integration. + `xo documentation here`_ + +.. _xo documentation here: https://rconybea.github.io/web/sw/xo.html + +.. toctree:: diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..108d97d4 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,40 @@ +.. xo-unit-examples documentation master file, created by + sphinx-quickstart on Wed Mar 6 23:32:27 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +xo-unit documentation +===================== + +xo-unit is a lightweight header-only library that provides compile-time +dimension checking and unit conversion. + +Functionality is similar in spirit to that provided by ``boost::units``; +however there are some important differences: + +First, implementation relies on c++20 features +like class-instance template parameters to efficiently assemble string constants at compile time. + +Second, ``xo-unit`` supports fractional dimensions. This allows using it to naturally handle +concepts like volatility (dimension 1/sqrt(time)), for example. + +Finally, ``xo-unit`` is written with the expectation of providing +python integration via pybind11. This requires a parallel set of data structures that can work at +runtime (since we can't construct new c++ types at runtime). + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + install + examples + classes + +Indices and Tables +------------------ + +* :ref:`glossary` +* :ref:`genindex` +* :ref:`search` + +.. toctree:: diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 00000000..d8e0f9bd --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,62 @@ +.. _install: + +.. toctree + :maxdepth: 2 + +Install +======= + +`xo-unit source`_ lives on github. + +.. _xo-unit source: https://github.com/rconybea/xo-unit + +Implementation relies on some c++20 features (for example class-instances as template arguments). +Tested with gcc 12.3 + +Include as submodule +-------------------- + +.. code-block:: bash + + cd myproject + git submodule add -b main https://github.com/rconybea/xo-unit ext/xo-unit + git submodule update --init + +This assumes you organize directly-incorporated dependencies under directory ``myproject/ext``. +You would then add ``myproject/ext/xo-unit/include`` to your compiler's include path, +and add + +.. code-block:: c++ + + #include + +to c++ source files that rely on xo-unit + +Supported compilers +------------------- + +* developed with gcc 12.3.0; github CI using gcc 11.4.0 (asof March 2024) + +Building from source +-------------------- + +Although the xo-unit library is header-only, unit tests have some dependencies. +Example instructions (github CI) for build starting from stock ubuntu are in `ubuntu-main.yml`_ + +.. _ubuntu-main.yml: https://github.com/Rconybea/xo-unit/blob/main/.github/workflows/ubuntu-main.yml + +Unit test dependencies: + +* `catch2`_ header-only unit-test framework +* `xo-cmake`_ cmake macros +* `xo-indentlog`_ logging with call-structure indenting +* `xo-refcnt`_ intrusive reference counting (needed by xo-reflect) +* `xo-subsys`_ plugin initialization support (needed by xo-reflect) +* `xo-reflect`_ c++ introspection library + +.. _catch2: https://github.com/catchorg/Catch2 +.. _xo-cmake: https://github.com/rconybea/xo-cmake +.. _xo-indentlog: https://github.com/rconybea/indentlog +.. _xo-refcnt: https://github.com/rconybea/refcnt +.. _xo-subsys: https://github.com/rconybea/subsys +.. _xo-reflect: https://github.com/rconybea/reflect From 0d9fd75b0c4462b5a46590861094cdfcb38a5967 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Apr 2024 17:29:35 -0400 Subject: [PATCH 0554/2524] xo-unit: docs: + example programs --- CMakeLists.txt | 1 + example/CMakeLists.txt | 3 +++ example/ex1/CMakeLists.txt | 14 ++++++++++++++ example/ex1/ex1.cpp | 18 ++++++++++++++++++ example/ex2/CMakeLists.txt | 14 ++++++++++++++ example/ex2/ex2.cpp | 20 ++++++++++++++++++++ example/ex3/CMakeLists.txt | 14 ++++++++++++++ example/ex3/ex3.cpp | 19 +++++++++++++++++++ 8 files changed, 103 insertions(+) create mode 100644 example/CMakeLists.txt create mode 100644 example/ex1/CMakeLists.txt create mode 100644 example/ex1/ex1.cpp create mode 100644 example/ex2/CMakeLists.txt create mode 100644 example/ex2/ex2.cpp create mode 100644 example/ex3/CMakeLists.txt create mode 100644 example/ex3/ex3.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a879e31f..fc595582 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ xo_toplevel_compile_options() # ---------------------------------------------------------------- #add_subdirectory(src/unit) +add_subdirectory(example) add_subdirectory(utest) add_subdirectory(docs) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 00000000..ed03faf2 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(ex1) +add_subdirectory(ex2) +add_subdirectory(ex3) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..0d415a06 --- /dev/null +++ b/example/ex1/CMakeLists.txt @@ -0,0 +1,14 @@ +# xo-unit/example/ex1/CMakeLists.txt + +set(SELF_EXE xo_unit_ex1) +set(SELF_SRCS ex1.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_unit) + +# end CMakeLists.txt diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp new file mode 100644 index 00000000..74d0dc10 --- /dev/null +++ b/example/ex1/ex1.cpp @@ -0,0 +1,18 @@ +/** @file ex1.cpp **/ + +#include "xo/unit/quantity.hpp" +#include + +int +main () { + namespace qty = xo::obs::qty; + using namespace std; + + auto t = qty::milliseconds(10); + auto m = qty::kilograms(2.5); + auto a = m / (t*t); + + cerr << "t: " << t << ", m: " << m << ", m.t^-2: " << a << endl; +} + +/** end ex1.cpp **/ diff --git a/example/ex2/CMakeLists.txt b/example/ex2/CMakeLists.txt new file mode 100644 index 00000000..fa98d33b --- /dev/null +++ b/example/ex2/CMakeLists.txt @@ -0,0 +1,14 @@ +# xo-unit/example/ex2/CMakeLists.txt + +set(SELF_EXE xo_unit_ex2) +set(SELF_SRCS ex2.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_unit) + +# end CMakeLists.txt diff --git a/example/ex2/ex2.cpp b/example/ex2/ex2.cpp new file mode 100644 index 00000000..96e574ed --- /dev/null +++ b/example/ex2/ex2.cpp @@ -0,0 +1,20 @@ +/** @file ex2.cpp **/ + +#include "xo/unit/quantity.hpp" +#include + +int +main () { + namespace u = xo::obs::units; + namespace qty = xo::obs::qty; + using xo::obs::quantity; + using namespace std; + + quantity t = qty::milliseconds(10); + quantity m = qty::kilograms(2.5); + auto a = m / (t*t); + + cerr << "t: " << t << ", m: " << m << ", m.t^-2: " << a << endl; +} + +/** end ex2.cpp **/ diff --git a/example/ex3/CMakeLists.txt b/example/ex3/CMakeLists.txt new file mode 100644 index 00000000..e6f40fc1 --- /dev/null +++ b/example/ex3/CMakeLists.txt @@ -0,0 +1,14 @@ +# xo-unit/example/ex3/CMakeLists.txt + +set(SELF_EXE xo_unit_ex3) +set(SELF_SRCS ex3.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_unit) + +# end CMakeLists.txt diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp new file mode 100644 index 00000000..d7939957 --- /dev/null +++ b/example/ex3/ex3.cpp @@ -0,0 +1,19 @@ +/** @file ex3.cpp **/ + +#include "xo/unit/quantity.hpp" +#include + +int +main () { + namespace u = xo::obs::units; + namespace qty = xo::obs::qty; + using xo::obs::quantity; + using namespace std; + + auto t1 = qty::milliseconds(1); + auto t2 = qty::minutes(1); + + cerr << "t1: " << t1 << ", t2: " << t2 << ", t1*t2: " << t1*t2 << endl; +} + +/** end ex3.cpp **/ From 183da6b29b48fbec6671f04358d3dde1c1c7bc58 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Apr 2024 17:29:52 -0400 Subject: [PATCH 0555/2524] xo-unit: README: instructions for building sphinx docs --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 68174012..5af84f03 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,13 @@ $ make $ make install ``` +### build documentation +``` +$ cd xo-unit +$ cmake --build .build -- sphinx +``` +When this completes, point local browser to `xo-unit/.build/docs/sphinx/index.html`. + ### build for unit test coverage ``` $ cd xo-unit From 13f4746f524312fb41910a2bf793d66032a93fa5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Apr 2024 17:30:16 -0400 Subject: [PATCH 0556/2524] xo-unit: docs: bugfix: missing sphinx conf.py --- docs/conf.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 docs/conf.py diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..a590afbe --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,35 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'xo unit documentation' +copyright = '2024, Roland Conybeare' +author = 'Roland Conybeare' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +#extensions = [] +extensions = [ "breathe", + "sphinx.ext.autodoc" # generate info from docstrings + ] + +# note: breathe requires doxygen xml output -> must have GENERATE_XML = YES in Doxyfile.in +# match project name in Doxyfile.in +breathe_default_project = "xodoxxml" + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +pygments_style = 'sphinx' + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +#html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] From eff31256f98b94026a917bde514dc4b2e3fc3c8a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Apr 2024 17:30:35 -0400 Subject: [PATCH 0557/2524] xo-unit: docs: bugfix: fix xo::unit::quantity ref --- docs/classes.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 docs/classes.rst diff --git a/docs/classes.rst b/docs/classes.rst new file mode 100644 index 00000000..1e3ac342 --- /dev/null +++ b/docs/classes.rst @@ -0,0 +1,10 @@ +.. _classes: + +.. toctree + :maxdepth: 2 + +Template Classes +================ + +.. doxygenclass:: xo::unit::quantity + :members: From 204c889281006c99261398bd43681dfd2c648cec Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Apr 2024 17:30:52 -0400 Subject: [PATCH 0558/2524] xo-unit: + LICENSE --- LICENSE | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cae3cb5d --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2024 Roland Conybeare , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. From eb7ae8a564afc799936efc0f329b64b4d2c074ba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 12:50:29 -0400 Subject: [PATCH 0559/2524] xo-unit: + nixpkgs candidate + flake.nix --- flake.nix | 514 +++++++++++++++++++++++++++++++++++++++++++++++ pkgs/xo-unit.nix | 37 ++++ 2 files changed, 551 insertions(+) create mode 100644 flake.nix create mode 100644 pkgs/xo-unit.nix diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..ed2b35f1 --- /dev/null +++ b/flake.nix @@ -0,0 +1,514 @@ +{ + description = "xo-unit: c++ compile-time dimension checking and unit conversion"; + + # Adapted from xo-nix2/flake.nix + + # MANIFESTO + # No build instructions in flake.nix + # - Following Jade Lovelace's advice + # - Build instructions are in pkgs/*.nix + # - Each pkgs/*.nix is intended to work 'like a .nix file in nixpkgs' + # I'm being lazy about source hashes, since flake.nix supplies them. + # + # Motivation (per JL) versus doing everything in flake.nix: + # - nixpkgs-ready + # - parameterized + # - overridable + # - still works if cross-compiling + # + # Instead: using flake.nix as entry point: + # - pin nixpkgs to a specific revision, for reproducibility + # - pin our candidate packages (pkgs/*.nix), for the same reason. + + # to determine specific hash for nixpkgs: + # 1. $ cd ~/proj/nixpkgs + # 2. $ git checkout release-23.05 + # 3. $ git fetch + # 4. $ git pull + # 5. $ git log -1 + # take this hash, then substitue for ${hash} in: + # inputs.nixpkgs.url = "https://github.com/NixOS/nixpkgs/archive/${hash}.tar.gz"; + # below + #inputs.nixpkgs.url = "https://github.com/NixOS/nixpkgs/archive/9a333eaa80901efe01df07eade2c16d183761fa3.tar.gz"; + + # as sbove but instead of {release-23.05} use {release-23.11} + # gcc -> 12.3.0 + # python -> 3.11.6 + # + inputs.nixpkgs.url = "https://github.com/NixOS/nixpkgs/archive/217b3e910660fbf603b0995a6d2c3992aef4cc37.tar.gz"; # asof 10mar2024 + #inputs.nixpkgs.url = "https://github.com/NixOS/nixpkgs/archive/4dd376f7943c64b522224a548d9cab5627b4d9d6.tar.gz"; + + # inputs.nixpkgs.url + # = "https://github.com/NixOS/nixpkgs/archive/fac3684647cc9d6dfb2a39f3f4b7cf5fc89c96b6.tar.gz"; # asof 8feb2024 + # fac3684647.. asof 17oct2023 + # instead of + # inputs.nixpkgs.url = "github:nixos/nixpkgs/23.05"; + + inputs.flake-utils.url = "github:numtide/flake-utils"; + + # To add a new package, visit placeholder-A .. placeholder-E + + inputs.xo-cmake-path = { type = "github"; owner = "Rconybea"; repo = "xo-cmake"; flake = false; }; + inputs.xo-indentlog-path = { type = "github"; owner = "Rconybea"; repo = "indentlog"; flake = false; }; + inputs.xo-refcnt-path = { type = "github"; owner = "Rconybea"; repo = "refcnt"; flake = false; }; + inputs.xo-subsys-path = { type = "github"; owner = "Rconybea"; repo = "subsys"; flake = false; }; + inputs.xo-randomgen-path = { type = "github"; owner = "Rconybea"; repo = "randomgen"; flake = false; }; + inputs.xo-ordinaltree-path = { type = "github"; owner = "Rconybea"; repo = "xo-ordinaltree"; flake = false; }; + inputs.xo-pyutil-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyutil"; flake = false; }; + inputs.xo-reflect-path = { type = "github"; owner = "Rconybea"; repo = "reflect"; flake = false; }; + inputs.xo-pyreflect-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyreflect"; flake = false; }; + inputs.xo-printjson-path = { type = "github"; owner = "Rconybea"; repo = "xo-printjson"; flake = false; }; + inputs.xo-pyprintjson-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyprintjson"; flake = false; }; + inputs.xo-callback-path = { type = "github"; owner = "Rconybea"; repo = "xo-callback"; flake = false; }; + inputs.xo-webutil-path = { type = "github"; owner = "Rconybea"; repo = "xo-webutil"; flake = false; }; + inputs.xo-pywebutil-path = { type = "github"; owner = "Rconybea"; repo = "xo-pywebutil"; flake = false; }; + inputs.xo-reactor-path = { type = "github"; owner = "Rconybea"; repo = "xo-reactor"; flake = false; }; + inputs.xo-pyreactor-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyreactor"; flake = false; }; + inputs.xo-simulator-path = { type = "github"; owner = "Rconybea"; repo = "xo-simulator"; flake = false; }; + inputs.xo-pysimulator-path = { type = "github"; owner = "Rconybea"; repo = "xo-pysimulator"; flake = false; }; + inputs.xo-distribution-path = { type = "github"; owner = "Rconybea"; repo = "xo-distribution"; flake = false; }; + inputs.xo-pydistribution-path = { type = "github"; owner = "Rconybea"; repo = "xo-pydistribution"; flake = false; }; + inputs.xo-process-path = { type = "github"; owner = "Rconybea"; repo = "xo-process"; flake = false; }; + inputs.xo-pyprocess-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyprocess"; flake = false; }; + inputs.xo-statistics-path = { type = "github"; owner = "Rconybea"; repo = "xo-statistics"; flake = false; }; + inputs.xo-kalmanfilter-path = { type = "github"; owner = "Rconybea"; repo = "xo-kalmanfilter"; flake = false; }; + inputs.xo-pykalmanfilter-path = { type = "github"; owner = "Rconybea"; repo = "xo-pykalmanfilter"; flake = false; }; + inputs.xo-websock-path = { type = "github"; owner = "Rconybea"; repo = "xo-websock"; flake = false; }; + inputs.xo-pywebsock-path = { type = "github"; owner = "Rconybea"; repo = "xo-pywebsock"; flake = false; }; + # placeholder-A + + outputs + = { self, + nixpkgs, + flake-utils, + xo-cmake-path, + xo-indentlog-path, + xo-refcnt-path, + xo-subsys-path, + xo-reflect-path, + xo-pyreflect-path, + xo-randomgen-path, + xo-ordinaltree-path, + xo-pyutil-path, + xo-printjson-path, + xo-pyprintjson-path, + xo-callback-path, + xo-webutil-path, + xo-pywebutil-path, + xo-reactor-path, + xo-pyreactor-path, + xo-simulator-path, + xo-pysimulator-path, + xo-distribution-path, + xo-pydistribution-path, + xo-process-path, + xo-pyprocess-path, + xo-statistics-path, + xo-kalmanfilter-path, + xo-pykalmanfilter-path, + xo-websock-path, + xo-pywebsock-path, + # placeholder-B + } : + # out :: system -> {packages, devShells} + let + out + = system : + let + pkgs = nixpkgs.legacyPackages.${system}; + + # could try using + # appliedOverlay = (pkgs.extend self.overlays.default) + # but it doesn't seem to work the way I expect, + # For example, wants to pickup 2.7.11 python for xo-pyutil ! + # + appliedOverlay = self.overlays.default pkgs pkgs; + + in + { + #xo-cmake-dir = "${self.packages.${system}.xo-cmake}/share/cmake"; + + # reminder: + # 'packages' comprises the output of this flake; + # each defn invokes a build + # ./pkgs/$example.nix + # using + # cmake-examples-$example-path + # above for source code + + packages.xo-cmake = appliedOverlay.xo-cmake; + packages.xo-indentlog = appliedOverlay.xo-indentlog; + packages.xo-refcnt = appliedOverlay.xo-refcnt; + packages.xo-subsys = appliedOverlay.xo-subsys; + packages.xo-randomgen = appliedOverlay.xo-randomgen; + packages.xo-ordinaltree = appliedOverlay.xo-ordinaltree; + packages.xo-pyutil = appliedOverlay.xo-pyutil; + packages.xo-reflect = appliedOverlay.xo-reflect; + packages.xo-pyreflect = appliedOverlay.xo-pyreflect; + packages.xo-printjson = appliedOverlay.xo-printjson; + packages.xo-pyprintjson = appliedOverlay.xo-pyprintjson; + packages.xo-callback = appliedOverlay.xo-callback; + packages.xo-webutil = appliedOverlay.xo-webutil; + packages.xo-pywebutil = appliedOverlay.xo-pywebutil; + packages.xo-reactor = appliedOverlay.xo-reactor; + packages.xo-pyreactor = appliedOverlay.xo-pyreactor; + packages.xo-simulator = appliedOverlay.xo-simulator; + packages.xo-pysimulator = appliedOverlay.xo-pysimulator; + packages.xo-distribution = appliedOverlay.xo-distribution; + packages.xo-pydistribution = appliedOverlay.xo-pydistribution; + packages.xo-process = appliedOverlay.xo-process; + packages.xo-pyprocess = appliedOverlay.xo-pyprocess; + packages.xo-statistics = appliedOverlay.xo-statistics; + packages.xo-kalmanfilter = appliedOverlay.xo-kalmanfilter; + packages.xo-pykalmanfilter = appliedOverlay.xo-pykalmanfilter; + packages.xo-websock = appliedOverlay.xo-websock; + packages.xo-pywebsock = appliedOverlay.xo-pywebsock; + # placeholder-C + + packages.xo-userenv = appliedOverlay.xo-userenv; + + devShells = appliedOverlay.devShells; + }; + in + flake-utils.lib.eachDefaultSystem + out + // + { + # introduce overlay to extend nixpkgs with our local packages, + # (which ofc are not present in nixpkgs, though same form would work if they were present) + # + overlays.default = final: prev: + ( + let + # can use + # $ nix-env -qaP | grep \.boost # show known boost versions + # $ nix-env -qaP | grep \.python.*Packages # show known python versions + + stdenv = prev.stdenv; + + boost = prev.boost182; + python = prev.python311Full; + pythonPackages = prev.python311Packages; + #doxygen = prev.doxygen; + + pybind11 = pythonPackages.pybind11; + #breathe = python3Packages.breathe; + #sphinx = python3Packages.sphinx; + #sphinx-rtd-theme = python3Packages.sphinx-rtd-theme; + + #extras1 = { boost = boost; }; + #extras2 = { boost = boost; python3Packages = python3Packages; pybind11 = pybind11; }; + #extras3 = { boost = boost; python3Packages = python3Packages; pybind11 = pybind11; doxygen = doxygen; }; + #extras4 = extras3 // { breathe = breathe; }; + + xo-cmake = + (prev.callPackage ./pkgs/xo-cmake.nix {}).overrideAttrs + (old: { src = xo-cmake-path; }); + + xo-indentlog = + (prev.callPackage ./pkgs/xo-indentlog.nix { xo-cmake = xo-cmake; }).overrideAttrs + (old: { src = xo-indentlog-path; }); + + xo-subsys = + (prev.callPackage ./pkgs/xo-subsys.nix { xo-cmake = xo-cmake; }).overrideAttrs + (old: { src = xo-subsys-path; }); + + xo-refcnt = + (prev.callPackage ./pkgs/xo-refcnt.nix { xo-cmake = xo-cmake; + xo-indentlog = xo-indentlog; }).overrideAttrs + (old: { src = xo-refcnt-path; }); + + xo-randomgen = + (prev.callPackage ./pkgs/xo-randomgen.nix { xo-cmake = xo-cmake; + xo-indentlog = xo-indentlog; }).overrideAttrs + (old: { src = xo-randomgen-path; }); + + xo-ordinaltree = + (prev.callPackage ./pkgs/xo-ordinaltree.nix { xo-cmake = xo-cmake; + xo-refcnt = xo-refcnt; + xo-randomgen = xo-randomgen; }).overrideAttrs + (old: { src = xo-ordinaltree-path; }); + + xo-pyutil = + (prev.callPackage ./pkgs/xo-pyutil.nix { xo-cmake = xo-cmake; + xo-refcnt = xo-refcnt; + python = python; + pybind11 = pybind11; + }).overrideAttrs + (old: { src = xo-pyutil-path; }); + + xo-reflect = + (prev.callPackage ./pkgs/xo-reflect.nix { xo-cmake = xo-cmake; + xo-subsys = xo-subsys; + xo-refcnt = xo-refcnt; }).overrideAttrs + (old: { src = xo-reflect-path; }); + + xo-pyreflect = + (prev.callPackage ./pkgs/xo-pyreflect.nix { xo-cmake = xo-cmake; + xo-refcnt = xo-refcnt; + xo-pyutil = xo-pyutil; + xo-reflect = xo-reflect; }).overrideAttrs + (old: { src = xo-pyreflect-path; }); + + xo-unit = + (prev.callPackage ./pkgs/xo-unit.nix { xo-cmake = xo-cmake; + xo-reflect = xo-reflect; }).overrideAttrs + (old: { src = ./.; }); + + xo-printjson = + (prev.callPackage ./pkgs/xo-printjson.nix { xo-cmake = xo-cmake; + xo-reflect = xo-reflect; }).overrideAttrs + (old: { src = xo-printjson-path; }); + + xo-pyprintjson = + (prev.callPackage ./pkgs/xo-pyprintjson.nix { xo-cmake = xo-cmake; + xo-pyutil = xo-pyutil; + xo-printjson = xo-printjson; + xo-pyreflect = xo-pyreflect; }).overrideAttrs + (old: { src = xo-pyprintjson-path; }); + + xo-callback = + (prev.callPackage ./pkgs/xo-callback.nix { xo-cmake = xo-cmake; + xo-reflect = xo-reflect; }).overrideAttrs + (old: { src = xo-callback-path; }); + + xo-webutil = + (prev.callPackage ./pkgs/xo-webutil.nix { xo-cmake = xo-cmake; + xo-reflect = xo-reflect; + xo-callback = xo-callback; }).overrideAttrs + (old: { src = xo-webutil-path; }); + + xo-pywebutil = + (prev.callPackage ./pkgs/xo-pywebutil.nix { xo-cmake = xo-cmake; + xo-webutil = xo-webutil; + xo-pyutil = xo-pyutil; }).overrideAttrs + (old: { src = xo-pywebutil-path; }); + + xo-reactor = + (prev.callPackage ./pkgs/xo-reactor.nix { xo-cmake = xo-cmake; + xo-randomgen = xo-randomgen; + xo-webutil = xo-webutil; + xo-printjson = xo-printjson; + xo-ordinaltree = xo-ordinaltree; }).overrideAttrs + (old: { src = xo-reactor-path; }); + + xo-pyreactor = + (prev.callPackage ./pkgs/xo-pyreactor.nix { xo-cmake = xo-cmake; + xo-reactor = xo-reactor; + xo-pyutil = xo-pyutil; + xo-pyreflect = xo-pyreflect; + xo-pyprintjson = xo-pyprintjson; + }).overrideAttrs + (old: { src = xo-pyreactor-path; }); + + xo-simulator = + (prev.callPackage ./pkgs/xo-simulator.nix { xo-cmake = xo-cmake; + xo-reactor = xo-reactor; + }).overrideAttrs + (old: { src = xo-simulator-path; }); + + xo-pysimulator = + (prev.callPackage ./pkgs/xo-pysimulator.nix { xo-cmake = xo-cmake; + xo-simulator = xo-simulator; + xo-pyutil = xo-pyutil; + xo-pyreactor = xo-pyreactor; + }).overrideAttrs + (old: { src = xo-pysimulator-path; }); + + xo-distribution = + (prev.callPackage ./pkgs/xo-distribution.nix { xo-cmake = xo-cmake; + xo-refcnt = xo-refcnt; + }).overrideAttrs + (old: { src = xo-distribution-path; }); + + xo-pydistribution = + (prev.callPackage ./pkgs/xo-pydistribution.nix { xo-cmake = xo-cmake; + xo-distribution = xo-distribution; + xo-pyutil = xo-pyutil; + }).overrideAttrs + (old: { src = xo-pydistribution-path; }); + + xo-process = + (prev.callPackage ./pkgs/xo-process.nix { xo-cmake = xo-cmake; + xo-simulator = xo-simulator; + xo-randomgen = xo-randomgen; + }).overrideAttrs + (old: { src = xo-process-path; }); + + xo-pyprocess = + (prev.callPackage ./pkgs/xo-pyprocess.nix { xo-cmake = xo-cmake; + xo-process = xo-process; + xo-pyutil = xo-pyutil; + xo-pywebutil = xo-pywebutil; + xo-pyreactor = xo-pyreactor; + }).overrideAttrs + (old: { src = xo-pyprocess-path; }); + + xo-statistics = + (prev.callPackage ./pkgs/xo-statistics.nix { xo-cmake = xo-cmake; + #xo-reactor = xo-reactor; + }).overrideAttrs + (old: { src = xo-statistics-path; }); + + xo-kalmanfilter = + (prev.callPackage ./pkgs/xo-kalmanfilter.nix { xo-cmake = xo-cmake; + xo-statistics = xo-statistics; + xo-reactor = xo-reactor; + }).overrideAttrs + (old: { src = xo-kalmanfilter-path; }); + + + xo-pykalmanfilter = + (prev.callPackage ./pkgs/xo-pykalmanfilter.nix { xo-cmake = xo-cmake; + xo-pyutil = xo-pyutil; + xo-kalmanfilter = xo-kalmanfilter; + xo-pyreactor = xo-pyreactor; + }).overrideAttrs + (old: { src = xo-pykalmanfilter-path; }); + + xo-websock = + (prev.callPackage ./pkgs/xo-websock.nix { xo-cmake = xo-cmake; + xo-reactor = xo-reactor; + }).overrideAttrs + (old: { src = xo-websock-path; }); + + xo-pywebsock = + (prev.callPackage ./pkgs/xo-pywebsock.nix { xo-cmake = xo-cmake; + xo-websock = xo-websock; + xo-pyutil = xo-pyutil; + xo-pywebutil = xo-pywebutil; + }).overrideAttrs + (old: { src = xo-pywebsock-path; }); + + # placeholder-D + + # user environment with all xo libraries present + xo-userenv = + (prev.callPackage ./pkgs/xo-userenv.nix { xo-cmake = xo-cmake; + xo-indentlog = xo-indentlog; + xo-callback = xo-callback; + xo-subsys = xo-subsys; + xo-refcnt = xo-refcnt; + xo-randomgen = xo-randomgen; + xo-ordinaltree = xo-ordinaltree; + xo-pyutil = xo-pyutil; + xo-reflect = xo-reflect; + xo-pyreflect = xo-pyreflect; + xo-unit = xo-unit; + xo-printjson = xo-printjson; + xo-pyprintjson = xo-pyprintjson; + xo-webutil = xo-webutil; + xo-pywebutil = xo-pywebutil; + xo-reactor = xo-reactor; + xo-pyreactor = xo-pyreactor; + xo-simulator = xo-simulator; + xo-pysimulator = xo-pysimulator; + xo-distribution = xo-distribution; + xo-pydistribution = xo-pydistribution; + xo-process = xo-process; + xo-pyprocess = xo-pyprocess; + xo-statistics = xo-statistics; + xo-kalmanfilter = xo-kalmanfilter; + xo-pykalmanfilter = xo-pykalmanfilter; + xo-websock = xo-websock; + xo-pywebsock = xo-pywebsock; + }).overrideAttrs(old: {}); + + + in + # attrs in this set provide derivations with all overlay changes applied. + # + # REMINDER: need expression like + # packages.xo-foo = appliedOverlay.xo-foo; + # above to export + { + xo-cmake = xo-cmake; + xo-indentlog = xo-indentlog; + xo-subsys = xo-subsys; + xo-refcnt = xo-refcnt; + xo-randomgen = xo-randomgen; + xo-ordinaltree = xo-ordinaltree; + xo-pyutil = xo-pyutil; + xo-reflect = xo-reflect; + xo-pyreflect = xo-pyreflect; + xo-unit = xo-unit; + xo-printjson = xo-printjson; + xo-pyprintjson = xo-pyprintjson; + xo-callback = xo-callback; + xo-webutil = xo-webutil; + xo-pywebutil = xo-pywebutil; + xo-reactor = xo-reactor; + xo-pyreactor = xo-pyreactor; + xo-simulator = xo-simulator; + xo-pysimulator = xo-pysimulator; + xo-distribution = xo-distribution; + xo-pydistribution = xo-pydistribution; + xo-process = xo-process; + xo-pyprocess = xo-pyprocess; + xo-statistics = xo-statistics; + xo-kalmanfilter = xo-kalmanfilter; + xo-pykalmanfilter = xo-pykalmanfilter; + xo-websock = xo-websock; + xo-pywebsock = xo-pywebsock; + # placeholder-E + + xo-userenv = xo-userenv; + + devShells = { + default = prev.mkShell.override + # but may need prev.clang16Stdenv instead of prev.stdenv here on macos + { stdenv = prev.stdenv; } + + { packages + = [ python + pybind11 + pythonPackages.coverage + pythonPackages.sphinx + pythonPackages.sphinx-rtd-theme + pythonPackages.breathe + # pythonPackages.pyarrow + boost # really for filemerge + + prev.llvmPackages_16.clang-unwrapped + + prev.anki + prev.mesa + prev.egl-wayland + + prev.emacs29 + prev.notmuch + prev.emacsPackages.notmuch + prev.inconsolata-lgc + + prev.doxygen + prev.graphviz + + prev.ditaa + prev.semgrep + prev.ripgrep + prev.git + prev.openssh + prev.cmake + prev.gdb + prev.which + prev.man + prev.man-pages + prev.less + prev.tree + prev.nix-tree + prev.lcov + + prev.arrow-cpp + prev.libwebsockets + prev.jsoncpp + prev.eigen + prev.catch2 + prev.pkg-config + prev.zlib + ]; + }; + }; + }); + }; + +} diff --git a/pkgs/xo-unit.nix b/pkgs/xo-unit.nix new file mode 100644 index 00000000..093c3091 --- /dev/null +++ b/pkgs/xo-unit.nix @@ -0,0 +1,37 @@ +{ + # nixpkgs dependencies + stdenv, cmake, catch2, # ... other deps here + + # xo dependencies + xo-cmake, xo-reflect, + + # args + + # attrset for fetching source code. + # { type, owner, repo, ref } + # + # e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b" + # + # see [[../flake.nix]] + # + #cmake-examples-ex1-path + + # someconfigurationoption ? false +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-unit"; + + src = (fetchGit { + url = "https://github.com/rconybea/xo-unit"; + version = "1.0"; + #ref = "ex1"; + #rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645"; + }); + + cmakeFlags = ["-DCMAKE_MODULE_PATH=${xo-cmake}/share/cmake"]; + doCheck = true; + nativeBuildInputs = [ cmake catch2 ]; + propagatedBuildInputs = [ ]; + }) From 0f202575f9dcf3b434314128418311297e2b8bd3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 12:52:19 -0400 Subject: [PATCH 0560/2524] + pkgs/xo-cmake.nix --- pkgs/xo-cmake.nix | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 pkgs/xo-cmake.nix diff --git a/pkgs/xo-cmake.nix b/pkgs/xo-cmake.nix new file mode 100644 index 00000000..91a8cbd2 --- /dev/null +++ b/pkgs/xo-cmake.nix @@ -0,0 +1,38 @@ +{ + # dependencies + stdenv, cmake, # ... other deps here + + # args + + # attrset for fetching source code. + # { type, owner, repo, ref } + # + # e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b" + # + # see [[../flake.nix]] + # + #cmake-examples-ex1-path + + # someconfigurationoption ? false +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-cmake"; + +# src = cmake-examples-ex1-path; + + src = (fetchGit { + url = "https://github.com/rconybea/xo-cmake"; + version = "1.0"; + #ref = "ex1"; + #rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645"; + }); + + nativeBuildInputs = [ cmake ]; + +# installPhase = '' +# mkdir -p $out +# echo 'This project intentionally has no install phase' +# ''; + }) From ac6b55c4478b70e6aedcd76892494304e6ad2c4d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 12:53:48 -0400 Subject: [PATCH 0561/2524] xo-unit: + ./pkgs/xo-indentlog.nix for nix CI --- pkgs/xo-indentlog.nix | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 pkgs/xo-indentlog.nix diff --git a/pkgs/xo-indentlog.nix b/pkgs/xo-indentlog.nix new file mode 100644 index 00000000..8277d455 --- /dev/null +++ b/pkgs/xo-indentlog.nix @@ -0,0 +1,36 @@ +{ + # nixpkgs dependencies + stdenv, cmake, catch2, # ... other deps here + + # xo dependencies + xo-cmake, + + # args + + # attrset for fetching source code. + # { type, owner, repo, ref } + # + # e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b" + # + # see [[../flake.nix]] + # + #cmake-examples-ex1-path + + # someconfigurationoption ? false +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-indentlog"; + + src = (fetchGit { + url = "https://github.com/rconybea/indentlog"; + version = "1.0"; + #ref = "ex1"; + #rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645"; + }); + + cmakeFlags = ["-DCMAKE_MODULE_PATH=${xo-cmake}/share/cmake"]; + doCheck = true; + nativeBuildInputs = [ cmake catch2 ]; + }) From 4cfad335784233d8bf99793f7ef8d626b94e3489 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 12:54:09 -0400 Subject: [PATCH 0562/2524] xo-unit: + ./pkgs/xo-refcnt.nix for nix CI --- pkgs/xo-refcnt.nix | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 pkgs/xo-refcnt.nix diff --git a/pkgs/xo-refcnt.nix b/pkgs/xo-refcnt.nix new file mode 100644 index 00000000..a571dd04 --- /dev/null +++ b/pkgs/xo-refcnt.nix @@ -0,0 +1,37 @@ +{ + # nixpkgs dependencies + stdenv, cmake, catch2, # ... other deps here + + # xo dependencies + xo-cmake, xo-indentlog + + # args + + # attrset for fetching source code. + # { type, owner, repo, ref } + # + # e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b" + # + # see [[../flake.nix]] + # + #cmake-examples-ex1-path + + # someconfigurationoption ? false +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-refcnt"; + + src = (fetchGit { + url = "https://github.com/rconybea/refcnt"; + version = "1.0"; + #ref = "ex1"; + #rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645"; + }); + + cmakeFlags = ["-DCMAKE_MODULE_PATH=${xo-cmake}/share/cmake"]; + doCheck = true; + nativeBuildInputs = [ cmake catch2 ]; + propagatedBuildInputs = [ xo-indentlog ]; + }) From f439108352deae1a61176e574694a3c500865155 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 12:54:31 -0400 Subject: [PATCH 0563/2524] xo-unit: + ./pkgs/xo-subsys.nix for nix CI --- pkgs/xo-subsys.nix | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 pkgs/xo-subsys.nix diff --git a/pkgs/xo-subsys.nix b/pkgs/xo-subsys.nix new file mode 100644 index 00000000..791fc341 --- /dev/null +++ b/pkgs/xo-subsys.nix @@ -0,0 +1,36 @@ +{ + # nixpkgs dependencies + stdenv, cmake, catch2, # ... other deps here + + # xo dependencies + xo-cmake, + + # args + + # attrset for fetching source code. + # { type, owner, repo, ref } + # + # e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b" + # + # see [[../flake.nix]] + # + #cmake-examples-ex1-path + + # someconfigurationoption ? false +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-subsys"; + + src = (fetchGit { + url = "https://github.com/rconybea/subsys"; + version = "1.0"; + #ref = "ex1"; + #rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645"; + }); + + cmakeFlags = ["-DCMAKE_MODULE_PATH=${xo-cmake}/share/cmake"]; + #doCheck = true; + nativeBuildInputs = [ cmake catch2 ]; + }) From e3325e09260be6304d1eadb9c8def017daacb7e6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 12:58:01 -0400 Subject: [PATCH 0564/2524] xo-unit: build: + pkgs/xo-reflect.nix for nix CI --- flake.nix | 264 +------------------------------------------- pkgs/xo-reflect.nix | 37 +++++++ 2 files changed, 40 insertions(+), 261 deletions(-) create mode 100644 pkgs/xo-reflect.nix diff --git a/flake.nix b/flake.nix index ed2b35f1..a54b4e28 100644 --- a/flake.nix +++ b/flake.nix @@ -52,29 +52,10 @@ inputs.xo-indentlog-path = { type = "github"; owner = "Rconybea"; repo = "indentlog"; flake = false; }; inputs.xo-refcnt-path = { type = "github"; owner = "Rconybea"; repo = "refcnt"; flake = false; }; inputs.xo-subsys-path = { type = "github"; owner = "Rconybea"; repo = "subsys"; flake = false; }; - inputs.xo-randomgen-path = { type = "github"; owner = "Rconybea"; repo = "randomgen"; flake = false; }; - inputs.xo-ordinaltree-path = { type = "github"; owner = "Rconybea"; repo = "xo-ordinaltree"; flake = false; }; - inputs.xo-pyutil-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyutil"; flake = false; }; + #inputs.xo-pyutil-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyutil"; flake = false; }; inputs.xo-reflect-path = { type = "github"; owner = "Rconybea"; repo = "reflect"; flake = false; }; - inputs.xo-pyreflect-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyreflect"; flake = false; }; - inputs.xo-printjson-path = { type = "github"; owner = "Rconybea"; repo = "xo-printjson"; flake = false; }; - inputs.xo-pyprintjson-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyprintjson"; flake = false; }; - inputs.xo-callback-path = { type = "github"; owner = "Rconybea"; repo = "xo-callback"; flake = false; }; - inputs.xo-webutil-path = { type = "github"; owner = "Rconybea"; repo = "xo-webutil"; flake = false; }; - inputs.xo-pywebutil-path = { type = "github"; owner = "Rconybea"; repo = "xo-pywebutil"; flake = false; }; - inputs.xo-reactor-path = { type = "github"; owner = "Rconybea"; repo = "xo-reactor"; flake = false; }; - inputs.xo-pyreactor-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyreactor"; flake = false; }; - inputs.xo-simulator-path = { type = "github"; owner = "Rconybea"; repo = "xo-simulator"; flake = false; }; - inputs.xo-pysimulator-path = { type = "github"; owner = "Rconybea"; repo = "xo-pysimulator"; flake = false; }; - inputs.xo-distribution-path = { type = "github"; owner = "Rconybea"; repo = "xo-distribution"; flake = false; }; - inputs.xo-pydistribution-path = { type = "github"; owner = "Rconybea"; repo = "xo-pydistribution"; flake = false; }; - inputs.xo-process-path = { type = "github"; owner = "Rconybea"; repo = "xo-process"; flake = false; }; - inputs.xo-pyprocess-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyprocess"; flake = false; }; - inputs.xo-statistics-path = { type = "github"; owner = "Rconybea"; repo = "xo-statistics"; flake = false; }; - inputs.xo-kalmanfilter-path = { type = "github"; owner = "Rconybea"; repo = "xo-kalmanfilter"; flake = false; }; - inputs.xo-pykalmanfilter-path = { type = "github"; owner = "Rconybea"; repo = "xo-pykalmanfilter"; flake = false; }; - inputs.xo-websock-path = { type = "github"; owner = "Rconybea"; repo = "xo-websock"; flake = false; }; - inputs.xo-pywebsock-path = { type = "github"; owner = "Rconybea"; repo = "xo-pywebsock"; flake = false; }; + #inputs.xo-pyreflect-path = { type = "github"; owner = "Rconybea"; repo = "xo-pyreflect"; flake = false; }; + # placeholder-A outputs @@ -86,28 +67,6 @@ xo-refcnt-path, xo-subsys-path, xo-reflect-path, - xo-pyreflect-path, - xo-randomgen-path, - xo-ordinaltree-path, - xo-pyutil-path, - xo-printjson-path, - xo-pyprintjson-path, - xo-callback-path, - xo-webutil-path, - xo-pywebutil-path, - xo-reactor-path, - xo-pyreactor-path, - xo-simulator-path, - xo-pysimulator-path, - xo-distribution-path, - xo-pydistribution-path, - xo-process-path, - xo-pyprocess-path, - xo-statistics-path, - xo-kalmanfilter-path, - xo-pykalmanfilter-path, - xo-websock-path, - xo-pywebsock-path, # placeholder-B } : # out :: system -> {packages, devShells} @@ -140,29 +99,7 @@ packages.xo-indentlog = appliedOverlay.xo-indentlog; packages.xo-refcnt = appliedOverlay.xo-refcnt; packages.xo-subsys = appliedOverlay.xo-subsys; - packages.xo-randomgen = appliedOverlay.xo-randomgen; - packages.xo-ordinaltree = appliedOverlay.xo-ordinaltree; - packages.xo-pyutil = appliedOverlay.xo-pyutil; packages.xo-reflect = appliedOverlay.xo-reflect; - packages.xo-pyreflect = appliedOverlay.xo-pyreflect; - packages.xo-printjson = appliedOverlay.xo-printjson; - packages.xo-pyprintjson = appliedOverlay.xo-pyprintjson; - packages.xo-callback = appliedOverlay.xo-callback; - packages.xo-webutil = appliedOverlay.xo-webutil; - packages.xo-pywebutil = appliedOverlay.xo-pywebutil; - packages.xo-reactor = appliedOverlay.xo-reactor; - packages.xo-pyreactor = appliedOverlay.xo-pyreactor; - packages.xo-simulator = appliedOverlay.xo-simulator; - packages.xo-pysimulator = appliedOverlay.xo-pysimulator; - packages.xo-distribution = appliedOverlay.xo-distribution; - packages.xo-pydistribution = appliedOverlay.xo-pydistribution; - packages.xo-process = appliedOverlay.xo-process; - packages.xo-pyprocess = appliedOverlay.xo-pyprocess; - packages.xo-statistics = appliedOverlay.xo-statistics; - packages.xo-kalmanfilter = appliedOverlay.xo-kalmanfilter; - packages.xo-pykalmanfilter = appliedOverlay.xo-pykalmanfilter; - packages.xo-websock = appliedOverlay.xo-websock; - packages.xo-pywebsock = appliedOverlay.xo-pywebsock; # placeholder-C packages.xo-userenv = appliedOverlay.xo-userenv; @@ -218,200 +155,27 @@ xo-indentlog = xo-indentlog; }).overrideAttrs (old: { src = xo-refcnt-path; }); - xo-randomgen = - (prev.callPackage ./pkgs/xo-randomgen.nix { xo-cmake = xo-cmake; - xo-indentlog = xo-indentlog; }).overrideAttrs - (old: { src = xo-randomgen-path; }); - - xo-ordinaltree = - (prev.callPackage ./pkgs/xo-ordinaltree.nix { xo-cmake = xo-cmake; - xo-refcnt = xo-refcnt; - xo-randomgen = xo-randomgen; }).overrideAttrs - (old: { src = xo-ordinaltree-path; }); - - xo-pyutil = - (prev.callPackage ./pkgs/xo-pyutil.nix { xo-cmake = xo-cmake; - xo-refcnt = xo-refcnt; - python = python; - pybind11 = pybind11; - }).overrideAttrs - (old: { src = xo-pyutil-path; }); - xo-reflect = (prev.callPackage ./pkgs/xo-reflect.nix { xo-cmake = xo-cmake; xo-subsys = xo-subsys; xo-refcnt = xo-refcnt; }).overrideAttrs (old: { src = xo-reflect-path; }); - xo-pyreflect = - (prev.callPackage ./pkgs/xo-pyreflect.nix { xo-cmake = xo-cmake; - xo-refcnt = xo-refcnt; - xo-pyutil = xo-pyutil; - xo-reflect = xo-reflect; }).overrideAttrs - (old: { src = xo-pyreflect-path; }); - xo-unit = (prev.callPackage ./pkgs/xo-unit.nix { xo-cmake = xo-cmake; xo-reflect = xo-reflect; }).overrideAttrs (old: { src = ./.; }); - xo-printjson = - (prev.callPackage ./pkgs/xo-printjson.nix { xo-cmake = xo-cmake; - xo-reflect = xo-reflect; }).overrideAttrs - (old: { src = xo-printjson-path; }); - - xo-pyprintjson = - (prev.callPackage ./pkgs/xo-pyprintjson.nix { xo-cmake = xo-cmake; - xo-pyutil = xo-pyutil; - xo-printjson = xo-printjson; - xo-pyreflect = xo-pyreflect; }).overrideAttrs - (old: { src = xo-pyprintjson-path; }); - - xo-callback = - (prev.callPackage ./pkgs/xo-callback.nix { xo-cmake = xo-cmake; - xo-reflect = xo-reflect; }).overrideAttrs - (old: { src = xo-callback-path; }); - - xo-webutil = - (prev.callPackage ./pkgs/xo-webutil.nix { xo-cmake = xo-cmake; - xo-reflect = xo-reflect; - xo-callback = xo-callback; }).overrideAttrs - (old: { src = xo-webutil-path; }); - - xo-pywebutil = - (prev.callPackage ./pkgs/xo-pywebutil.nix { xo-cmake = xo-cmake; - xo-webutil = xo-webutil; - xo-pyutil = xo-pyutil; }).overrideAttrs - (old: { src = xo-pywebutil-path; }); - - xo-reactor = - (prev.callPackage ./pkgs/xo-reactor.nix { xo-cmake = xo-cmake; - xo-randomgen = xo-randomgen; - xo-webutil = xo-webutil; - xo-printjson = xo-printjson; - xo-ordinaltree = xo-ordinaltree; }).overrideAttrs - (old: { src = xo-reactor-path; }); - - xo-pyreactor = - (prev.callPackage ./pkgs/xo-pyreactor.nix { xo-cmake = xo-cmake; - xo-reactor = xo-reactor; - xo-pyutil = xo-pyutil; - xo-pyreflect = xo-pyreflect; - xo-pyprintjson = xo-pyprintjson; - }).overrideAttrs - (old: { src = xo-pyreactor-path; }); - - xo-simulator = - (prev.callPackage ./pkgs/xo-simulator.nix { xo-cmake = xo-cmake; - xo-reactor = xo-reactor; - }).overrideAttrs - (old: { src = xo-simulator-path; }); - - xo-pysimulator = - (prev.callPackage ./pkgs/xo-pysimulator.nix { xo-cmake = xo-cmake; - xo-simulator = xo-simulator; - xo-pyutil = xo-pyutil; - xo-pyreactor = xo-pyreactor; - }).overrideAttrs - (old: { src = xo-pysimulator-path; }); - - xo-distribution = - (prev.callPackage ./pkgs/xo-distribution.nix { xo-cmake = xo-cmake; - xo-refcnt = xo-refcnt; - }).overrideAttrs - (old: { src = xo-distribution-path; }); - - xo-pydistribution = - (prev.callPackage ./pkgs/xo-pydistribution.nix { xo-cmake = xo-cmake; - xo-distribution = xo-distribution; - xo-pyutil = xo-pyutil; - }).overrideAttrs - (old: { src = xo-pydistribution-path; }); - - xo-process = - (prev.callPackage ./pkgs/xo-process.nix { xo-cmake = xo-cmake; - xo-simulator = xo-simulator; - xo-randomgen = xo-randomgen; - }).overrideAttrs - (old: { src = xo-process-path; }); - - xo-pyprocess = - (prev.callPackage ./pkgs/xo-pyprocess.nix { xo-cmake = xo-cmake; - xo-process = xo-process; - xo-pyutil = xo-pyutil; - xo-pywebutil = xo-pywebutil; - xo-pyreactor = xo-pyreactor; - }).overrideAttrs - (old: { src = xo-pyprocess-path; }); - - xo-statistics = - (prev.callPackage ./pkgs/xo-statistics.nix { xo-cmake = xo-cmake; - #xo-reactor = xo-reactor; - }).overrideAttrs - (old: { src = xo-statistics-path; }); - - xo-kalmanfilter = - (prev.callPackage ./pkgs/xo-kalmanfilter.nix { xo-cmake = xo-cmake; - xo-statistics = xo-statistics; - xo-reactor = xo-reactor; - }).overrideAttrs - (old: { src = xo-kalmanfilter-path; }); - - - xo-pykalmanfilter = - (prev.callPackage ./pkgs/xo-pykalmanfilter.nix { xo-cmake = xo-cmake; - xo-pyutil = xo-pyutil; - xo-kalmanfilter = xo-kalmanfilter; - xo-pyreactor = xo-pyreactor; - }).overrideAttrs - (old: { src = xo-pykalmanfilter-path; }); - - xo-websock = - (prev.callPackage ./pkgs/xo-websock.nix { xo-cmake = xo-cmake; - xo-reactor = xo-reactor; - }).overrideAttrs - (old: { src = xo-websock-path; }); - - xo-pywebsock = - (prev.callPackage ./pkgs/xo-pywebsock.nix { xo-cmake = xo-cmake; - xo-websock = xo-websock; - xo-pyutil = xo-pyutil; - xo-pywebutil = xo-pywebutil; - }).overrideAttrs - (old: { src = xo-pywebsock-path; }); - # placeholder-D # user environment with all xo libraries present xo-userenv = (prev.callPackage ./pkgs/xo-userenv.nix { xo-cmake = xo-cmake; xo-indentlog = xo-indentlog; - xo-callback = xo-callback; xo-subsys = xo-subsys; xo-refcnt = xo-refcnt; - xo-randomgen = xo-randomgen; - xo-ordinaltree = xo-ordinaltree; - xo-pyutil = xo-pyutil; xo-reflect = xo-reflect; - xo-pyreflect = xo-pyreflect; xo-unit = xo-unit; - xo-printjson = xo-printjson; - xo-pyprintjson = xo-pyprintjson; - xo-webutil = xo-webutil; - xo-pywebutil = xo-pywebutil; - xo-reactor = xo-reactor; - xo-pyreactor = xo-pyreactor; - xo-simulator = xo-simulator; - xo-pysimulator = xo-pysimulator; - xo-distribution = xo-distribution; - xo-pydistribution = xo-pydistribution; - xo-process = xo-process; - xo-pyprocess = xo-pyprocess; - xo-statistics = xo-statistics; - xo-kalmanfilter = xo-kalmanfilter; - xo-pykalmanfilter = xo-pykalmanfilter; - xo-websock = xo-websock; - xo-pywebsock = xo-pywebsock; }).overrideAttrs(old: {}); @@ -426,30 +190,8 @@ xo-indentlog = xo-indentlog; xo-subsys = xo-subsys; xo-refcnt = xo-refcnt; - xo-randomgen = xo-randomgen; - xo-ordinaltree = xo-ordinaltree; - xo-pyutil = xo-pyutil; xo-reflect = xo-reflect; - xo-pyreflect = xo-pyreflect; xo-unit = xo-unit; - xo-printjson = xo-printjson; - xo-pyprintjson = xo-pyprintjson; - xo-callback = xo-callback; - xo-webutil = xo-webutil; - xo-pywebutil = xo-pywebutil; - xo-reactor = xo-reactor; - xo-pyreactor = xo-pyreactor; - xo-simulator = xo-simulator; - xo-pysimulator = xo-pysimulator; - xo-distribution = xo-distribution; - xo-pydistribution = xo-pydistribution; - xo-process = xo-process; - xo-pyprocess = xo-pyprocess; - xo-statistics = xo-statistics; - xo-kalmanfilter = xo-kalmanfilter; - xo-pykalmanfilter = xo-pykalmanfilter; - xo-websock = xo-websock; - xo-pywebsock = xo-pywebsock; # placeholder-E xo-userenv = xo-userenv; diff --git a/pkgs/xo-reflect.nix b/pkgs/xo-reflect.nix new file mode 100644 index 00000000..9f151c5f --- /dev/null +++ b/pkgs/xo-reflect.nix @@ -0,0 +1,37 @@ +{ + # nixpkgs dependencies + stdenv, cmake, catch2, # ... other deps here + + # xo dependencies + xo-cmake, xo-refcnt, xo-subsys, + + # args + + # attrset for fetching source code. + # { type, owner, repo, ref } + # + # e.g. type="github", owner="rconybea", repo="cmake-examples", ref="ex1b" + # + # see [[../flake.nix]] + # + #cmake-examples-ex1-path + + # someconfigurationoption ? false +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-reflect"; + + src = (fetchGit { + url = "https://github.com/rconybea/reflect"; + version = "1.0"; + #ref = "ex1"; + #rev = "c0472c9d7e4d2c53bfb977d3182380832fe96645"; + }); + + cmakeFlags = ["-DCMAKE_MODULE_PATH=${xo-cmake}/share/cmake"]; + doCheck = true; + nativeBuildInputs = [ cmake catch2 ]; + propagatedBuildInputs = [ xo-subsys xo-refcnt ]; + }) From cfd77c388856e313cd8f454af4d5596c0ade00e6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 13:00:30 -0400 Subject: [PATCH 0565/2524] xo-unit: + ./pkgs/xo-userenv for nix CI --- pkgs/xo-userenv.nix | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 pkgs/xo-userenv.nix diff --git a/pkgs/xo-userenv.nix b/pkgs/xo-userenv.nix new file mode 100644 index 00000000..9a622b55 --- /dev/null +++ b/pkgs/xo-userenv.nix @@ -0,0 +1,24 @@ +{ + # nixpkgs dependencies + buildFHSUserEnv, # ... other deps here + + # xo dependencies + xo-cmake, xo-indentlog, xo-subsys, xo-refcnt, xo-reflect, xo-unit, + + # other args + + # someconfigurationoption ? false +} : + +buildFHSUserEnv { + name = "xo-userenv"; + targetPkgs = pkgs: [ xo-cmake + xo-indentlog + xo-subsys + xo-refcnt + xo-reflect + xo-unit + ]; + # runScript = ...; + # profile = ...; +} From 9ddee6c61cff937320a7c9442a4dc71caefc79ef Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 13:01:10 -0400 Subject: [PATCH 0566/2524] xo-unit: + flake.lock file --- flake.lock | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 flake.lock diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..c4d40ddd --- /dev/null +++ b/flake.lock @@ -0,0 +1,141 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "narHash": "sha256-ci7ghtn0YKXw68Wkufou0DK3pwTmkfWeFOYkRsnLagc=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/217b3e910660fbf603b0995a6d2c3992aef4cc37.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/217b3e910660fbf603b0995a6d2c3992aef4cc37.tar.gz" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "xo-cmake-path": "xo-cmake-path", + "xo-indentlog-path": "xo-indentlog-path", + "xo-refcnt-path": "xo-refcnt-path", + "xo-reflect-path": "xo-reflect-path", + "xo-subsys-path": "xo-subsys-path" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "xo-cmake-path": { + "flake": false, + "locked": { + "lastModified": 1712015268, + "narHash": "sha256-+nu4z5LA1nSE8NwQ0l0AhhykUO9B2b5BvEs1mLNiI3Y=", + "owner": "Rconybea", + "repo": "xo-cmake", + "rev": "b8f2360b899739b72272036324a6997aa9169c8f", + "type": "github" + }, + "original": { + "owner": "Rconybea", + "repo": "xo-cmake", + "type": "github" + } + }, + "xo-indentlog-path": { + "flake": false, + "locked": { + "lastModified": 1712008884, + "narHash": "sha256-WGe9fQq6PM9CbZuA0maJOxOErN71l9J1/cHPigkLJoU=", + "owner": "Rconybea", + "repo": "indentlog", + "rev": "20774158ad3cd2f5feadecf9b994972443538ef7", + "type": "github" + }, + "original": { + "owner": "Rconybea", + "repo": "indentlog", + "type": "github" + } + }, + "xo-refcnt-path": { + "flake": false, + "locked": { + "lastModified": 1711737792, + "narHash": "sha256-Grel2sXne5LUGNqlcxcrbhqstXXAQxhQsXpd5Rcjn7E=", + "owner": "Rconybea", + "repo": "refcnt", + "rev": "cffffdae7c15273dfad8e04acf64193e644bda81", + "type": "github" + }, + "original": { + "owner": "Rconybea", + "repo": "refcnt", + "type": "github" + } + }, + "xo-reflect-path": { + "flake": false, + "locked": { + "lastModified": 1710545203, + "narHash": "sha256-L3421dvkTtibSGchOjj4RdCITtnlX8+4rU3XEnakf1s=", + "owner": "Rconybea", + "repo": "reflect", + "rev": "fe986a320a2b1993a8399602cb3a3b6dc8c86253", + "type": "github" + }, + "original": { + "owner": "Rconybea", + "repo": "reflect", + "type": "github" + } + }, + "xo-subsys-path": { + "flake": false, + "locked": { + "lastModified": 1711737221, + "narHash": "sha256-7VjuMxme3wmht32nzRjw6XhzfXu4szmNZjErDqCp6E8=", + "owner": "Rconybea", + "repo": "subsys", + "rev": "06bd4324305996db9b24a98c010a63c4593c1e21", + "type": "github" + }, + "original": { + "owner": "Rconybea", + "repo": "subsys", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} From 3f9e5f11cae0acc3c2a54efe84ea1bec4895839f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 13:02:29 -0400 Subject: [PATCH 0567/2524] xo-unit: bugfix: must not commit flake.lock for self --- flake.lock | 141 ----------------------------------------------------- 1 file changed, 141 deletions(-) delete mode 100644 flake.lock diff --git a/flake.lock b/flake.lock deleted file mode 100644 index c4d40ddd..00000000 --- a/flake.lock +++ /dev/null @@ -1,141 +0,0 @@ -{ - "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "narHash": "sha256-ci7ghtn0YKXw68Wkufou0DK3pwTmkfWeFOYkRsnLagc=", - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/217b3e910660fbf603b0995a6d2c3992aef4cc37.tar.gz" - }, - "original": { - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/217b3e910660fbf603b0995a6d2c3992aef4cc37.tar.gz" - } - }, - "root": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", - "xo-cmake-path": "xo-cmake-path", - "xo-indentlog-path": "xo-indentlog-path", - "xo-refcnt-path": "xo-refcnt-path", - "xo-reflect-path": "xo-reflect-path", - "xo-subsys-path": "xo-subsys-path" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "xo-cmake-path": { - "flake": false, - "locked": { - "lastModified": 1712015268, - "narHash": "sha256-+nu4z5LA1nSE8NwQ0l0AhhykUO9B2b5BvEs1mLNiI3Y=", - "owner": "Rconybea", - "repo": "xo-cmake", - "rev": "b8f2360b899739b72272036324a6997aa9169c8f", - "type": "github" - }, - "original": { - "owner": "Rconybea", - "repo": "xo-cmake", - "type": "github" - } - }, - "xo-indentlog-path": { - "flake": false, - "locked": { - "lastModified": 1712008884, - "narHash": "sha256-WGe9fQq6PM9CbZuA0maJOxOErN71l9J1/cHPigkLJoU=", - "owner": "Rconybea", - "repo": "indentlog", - "rev": "20774158ad3cd2f5feadecf9b994972443538ef7", - "type": "github" - }, - "original": { - "owner": "Rconybea", - "repo": "indentlog", - "type": "github" - } - }, - "xo-refcnt-path": { - "flake": false, - "locked": { - "lastModified": 1711737792, - "narHash": "sha256-Grel2sXne5LUGNqlcxcrbhqstXXAQxhQsXpd5Rcjn7E=", - "owner": "Rconybea", - "repo": "refcnt", - "rev": "cffffdae7c15273dfad8e04acf64193e644bda81", - "type": "github" - }, - "original": { - "owner": "Rconybea", - "repo": "refcnt", - "type": "github" - } - }, - "xo-reflect-path": { - "flake": false, - "locked": { - "lastModified": 1710545203, - "narHash": "sha256-L3421dvkTtibSGchOjj4RdCITtnlX8+4rU3XEnakf1s=", - "owner": "Rconybea", - "repo": "reflect", - "rev": "fe986a320a2b1993a8399602cb3a3b6dc8c86253", - "type": "github" - }, - "original": { - "owner": "Rconybea", - "repo": "reflect", - "type": "github" - } - }, - "xo-subsys-path": { - "flake": false, - "locked": { - "lastModified": 1711737221, - "narHash": "sha256-7VjuMxme3wmht32nzRjw6XhzfXu4szmNZjErDqCp6E8=", - "owner": "Rconybea", - "repo": "subsys", - "rev": "06bd4324305996db9b24a98c010a63c4593c1e21", - "type": "github" - }, - "original": { - "owner": "Rconybea", - "repo": "subsys", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} From f8e337c783acd3b68bdb1f079ba651ca22d2aa36 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 13:03:33 -0400 Subject: [PATCH 0568/2524] xo-unit: build: .gitignore ++ flake.lock --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 13c0afb7..e1c0d400 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# flake.nix builds source in this tree. +# must not commit flake.lock that would need hash from this repo +flake.lock # clangd working space (see emacs+lsp) .cache # typical cmake build directory (source-tree-nephew) From 554edcd045fe48e201797f959eedeb771f50cbdd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 13:04:46 -0400 Subject: [PATCH 0569/2524] looks like nix flakes needs not-git-ignored flake.lock --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index e1c0d400..13c0afb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -# flake.nix builds source in this tree. -# must not commit flake.lock that would need hash from this repo -flake.lock # clangd working space (see emacs+lsp) .cache # typical cmake build directory (source-tree-nephew) From d446f0671c9e007bc9f3faa6d7ed0165b6e4754f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 13:07:20 -0400 Subject: [PATCH 0570/2524] xo-unit: bugfix in flake.nix, must export xo-unit --- flake.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/flake.nix b/flake.nix index a54b4e28..c5ea5b37 100644 --- a/flake.nix +++ b/flake.nix @@ -100,6 +100,7 @@ packages.xo-refcnt = appliedOverlay.xo-refcnt; packages.xo-subsys = appliedOverlay.xo-subsys; packages.xo-reflect = appliedOverlay.xo-reflect; + packages.xo-unit = appliedOverlay.xo-unit; # placeholder-C packages.xo-userenv = appliedOverlay.xo-userenv; From 4446fc3e1e31fc96f2736985a2af6b216254a11c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 14:58:11 -0400 Subject: [PATCH 0571/2524] xo-unit: docs: + _static dir --- docs/_static/README | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/_static/README diff --git a/docs/_static/README b/docs/_static/README new file mode 100644 index 00000000..8230095c --- /dev/null +++ b/docs/_static/README @@ -0,0 +1 @@ +add any static {.html, .js, ..} files for sphinx to pickup here \ No newline at end of file From 7432d3798cb6848d5b4262173486f5a878978477 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 14:59:20 -0400 Subject: [PATCH 0572/2524] xo-unit: bugfix: xo::obs -> xo::unit in examples --- example/ex1/ex1.cpp | 2 +- example/ex2/ex2.cpp | 6 +++--- example/ex3/ex3.cpp | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 74d0dc10..2e286e7c 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -5,7 +5,7 @@ int main () { - namespace qty = xo::obs::qty; + namespace qty = xo::unit::qty; using namespace std; auto t = qty::milliseconds(10); diff --git a/example/ex2/ex2.cpp b/example/ex2/ex2.cpp index 96e574ed..2caf4ee3 100644 --- a/example/ex2/ex2.cpp +++ b/example/ex2/ex2.cpp @@ -5,9 +5,9 @@ int main () { - namespace u = xo::obs::units; - namespace qty = xo::obs::qty; - using xo::obs::quantity; + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using xo::unit::quantity; using namespace std; quantity t = qty::milliseconds(10); diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index d7939957..77fcf03e 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -5,9 +5,9 @@ int main () { - namespace u = xo::obs::units; - namespace qty = xo::obs::qty; - using xo::obs::quantity; + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using xo::unit::quantity; using namespace std; auto t1 = qty::milliseconds(1); From bde506df1c3974ae605c82ac715962027bd41179 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 15:00:01 -0400 Subject: [PATCH 0573/2524] xo-unit: build: + custom 'docs' target, same as 'sphinx' --- docs/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index bc00ca12..8c7b9b92 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -94,4 +94,9 @@ else() DESTINATION ${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME} COMPONENT Documentation OPTIONAL) + + # make docs --> generate sphinx documentation + add_custom_target( + docs + DEPENDS sphinx) endif() From 439c2d03c2078f976441d2ab4ac24e6a327a0938 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 15:00:26 -0400 Subject: [PATCH 0574/2524] xo-unit: docs: Helvetica -> HelveticaNeue --- docs/Doxyfile.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index 67b45f8c..8d1f25ba 100644 --- a/docs/Doxyfile.in +++ b/docs/Doxyfile.in @@ -2503,7 +2503,7 @@ DOT_NUM_THREADS = 0 # The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" +DOT_COMMON_ATTR = "fontname=HelveticaNeue,fontsize=10" # DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can # add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Date: Wed, 3 Apr 2024 15:01:42 -0400 Subject: [PATCH 0575/2524] xo-unit: nix: build docs --- flake.nix | 11 ++++++---- pkgs/xo-unit.nix | 52 ++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/flake.nix b/flake.nix index c5ea5b37..44369377 100644 --- a/flake.nix +++ b/flake.nix @@ -130,9 +130,9 @@ #doxygen = prev.doxygen; pybind11 = pythonPackages.pybind11; - #breathe = python3Packages.breathe; - #sphinx = python3Packages.sphinx; - #sphinx-rtd-theme = python3Packages.sphinx-rtd-theme; + #breathe = pythonPackages.breathe; + #sphinx = pythonPackages.sphinx; + #sphinx-rtd-theme = pythonPackages.sphinx-rtd-theme; #extras1 = { boost = boost; }; #extras2 = { boost = boost; python3Packages = python3Packages; pybind11 = pybind11; }; @@ -164,7 +164,10 @@ xo-unit = (prev.callPackage ./pkgs/xo-unit.nix { xo-cmake = xo-cmake; - xo-reflect = xo-reflect; }).overrideAttrs + xo-reflect = xo-reflect; + python = python; + pythonPackages = pythonPackages; + }).overrideAttrs (old: { src = ./.; }); # placeholder-D diff --git a/pkgs/xo-unit.nix b/pkgs/xo-unit.nix index 093c3091..e3ee6d43 100644 --- a/pkgs/xo-unit.nix +++ b/pkgs/xo-unit.nix @@ -1,8 +1,27 @@ { - # nixpkgs dependencies + # 1. nixpkgs dependencies + # 1.1. python + python, pythonPackages, + + # 1.2. particular python packages + sphinx ? pythonPackages.sphinx, + sphinx-rtd-theme ? pythonPackages.sphinx-rtd-theme, + breathe ? pythonPackages.breathe, + + # 1.3. document-generation packages + # use of makeFontsConf adapted from nixpkgs/development/libraries/gtkmm/4.x.nix + # + doxygen, graphviz, + #fontconfig, + makeFontsConf, + + # used for graphviz (dot) see docs/Doxyfile.in + helvetica-neue-lt-std, + + # 1.4. c++ build/utest chain stdenv, cmake, catch2, # ... other deps here - # xo dependencies + # 2. xo dependencies xo-cmake, xo-reflect, # args @@ -31,7 +50,32 @@ stdenv.mkDerivation (finalattrs: }); cmakeFlags = ["-DCMAKE_MODULE_PATH=${xo-cmake}/share/cmake"]; + + FONTCONFIG_FILE = makeFontsConf { fontDirectories = [ helvetica-neue-lt-std ]; }; + + # move HOME so fontconfig can do sensible things + buildPhase = '' + set -x + + echo "FONTCONFIG_FILE=$FONTCONFIG_FILE" + + #export FONTCONFIG_FILE=$fontconfig.out/etc/fonts/fonts.conf + export HOME=$TMPDIR + export XDG_CONFIG_HOME=$TMPDIR + + mkdir $XDG_CONFIG_HOME/fontconfig + + #grep xdg $FONTCONFIG_FILE + + #$fontconfig.out/bin/fc-cache -v + + make && make docs + ''; + + # NOTE: helvetic-neue-lt-std has unfree license + #FONTCONFIG_FILE = makeFontsConf { fontDirectories = [ helvetica-neue-lt-std ]; }; + doCheck = true; - nativeBuildInputs = [ cmake catch2 ]; - propagatedBuildInputs = [ ]; + nativeBuildInputs = [ cmake catch2 doxygen graphviz sphinx sphinx-rtd-theme breathe ]; + propagatedBuildInputs = [ xo-reflect ]; }) From b6467d0013b96073a27838c11c9735c9b5b8bd8c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 15:09:07 -0400 Subject: [PATCH 0576/2524] github: + nix workflow --- .github/workflows/nix-main.yml | 50 ++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/nix-main.yml diff --git a/.github/workflows/nix-main.yml b/.github/workflows/nix-main.yml new file mode 100644 index 00000000..7154fc3c --- /dev/null +++ b/.github/workflows/nix-main.yml @@ -0,0 +1,50 @@ +# Workflow to build xo-unit using custom docker container; +# container provides nix support +# +# NOTES +# 1. GIT_TOKEN granted automatically by github. +# has read permission on public resources + read/write permission on this repo +# +# 2. container built from [[https:github.com:rconybea/docker-nix-builder]] +# Includes dependencies: +# - nix +# - compiler toolchain: gcc, binutils, bash, etc +# - git +# - cmake +# - catch2 +# - pybind11 + python +# - libwebsockets +# - jsoncpp +# +name: xo-unit nix builder + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + BUILD_TYPE: Release + +jobs: + build_job: + name: xo-unit nix build on docker-nix-builder + runs-on: ubuntu-latest + container: + # custom docker image. see github.com:rconybea/docker-nix-builder for definition + image: ghcr.io/rconybea/docker-nix-builder:v1 + + steps: + # not using usual checkout actions: they don't work out-of-the-box from within a container + + - name: xo-unix + run: | + echo "::group::clone xo-unit repo" + mkdir -p repo + GIT_SSL_NO_VERIFY=true git clone https://${{env.GIT_USER}}:${{env.GIT_TOKEN}}@github.com/rconybea/xo-unix.git repo + echo "::endgroup" + + echo "::group::build xo-unit with nix" + (cd repo/xo-unit && nix build -L --print-build-logs .#xo-unit && tree ./result) + echo "::endgroup" From fc7a2564f09edcc32f7808401ab30eda15bcc43b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 15:10:32 -0400 Subject: [PATCH 0577/2524] github: lame workflow typo, sheesh! --- .github/workflows/nix-main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nix-main.yml b/.github/workflows/nix-main.yml index 7154fc3c..83d8af73 100644 --- a/.github/workflows/nix-main.yml +++ b/.github/workflows/nix-main.yml @@ -38,11 +38,11 @@ jobs: steps: # not using usual checkout actions: they don't work out-of-the-box from within a container - - name: xo-unix + - name: xo-unit run: | echo "::group::clone xo-unit repo" mkdir -p repo - GIT_SSL_NO_VERIFY=true git clone https://${{env.GIT_USER}}:${{env.GIT_TOKEN}}@github.com/rconybea/xo-unix.git repo + GIT_SSL_NO_VERIFY=true git clone https://${{env.GIT_USER}}:${{env.GIT_TOKEN}}@github.com/rconybea/xo-unit.git repo echo "::endgroup" echo "::group::build xo-unit with nix" From 52f8aec04f6ebaa01fa34986ada79bd88db8a97a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 15:12:00 -0400 Subject: [PATCH 0578/2524] github: bugfix in nix workflow --- .github/workflows/nix-main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix-main.yml b/.github/workflows/nix-main.yml index 83d8af73..590f5f84 100644 --- a/.github/workflows/nix-main.yml +++ b/.github/workflows/nix-main.yml @@ -42,7 +42,7 @@ jobs: run: | echo "::group::clone xo-unit repo" mkdir -p repo - GIT_SSL_NO_VERIFY=true git clone https://${{env.GIT_USER}}:${{env.GIT_TOKEN}}@github.com/rconybea/xo-unit.git repo + GIT_SSL_NO_VERIFY=true git clone https://${{env.GIT_USER}}:${{env.GIT_TOKEN}}@github.com/rconybea/xo-unit.git repo/xo-unit echo "::endgroup" echo "::group::build xo-unit with nix" From d2a8d9bbe55a77a1b707193e23ff92f63aae4e81 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 15:14:26 -0400 Subject: [PATCH 0579/2524] xo-unit: github: allow unfree helvetica-neue font --- .github/workflows/nix-main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nix-main.yml b/.github/workflows/nix-main.yml index 590f5f84..f0ae63e3 100644 --- a/.github/workflows/nix-main.yml +++ b/.github/workflows/nix-main.yml @@ -46,5 +46,6 @@ jobs: echo "::endgroup" echo "::group::build xo-unit with nix" - (cd repo/xo-unit && nix build -L --print-build-logs .#xo-unit && tree ./result) + export NIXPKGS_ALLOW_UNFREE=1 + (cd repo/xo-unit && nix build --impure -L --print-build-logs .#xo-unit && tree ./result) echo "::endgroup" From 0f7a739e2fdb51354a28f34546fe7de56713b389 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 17:23:05 -0400 Subject: [PATCH 0580/2524] xo-unit: docs: switch doxygen label font Helvetica->lato --- docs/Doxyfile.in | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index 8d1f25ba..13dfb0fa 100644 --- a/docs/Doxyfile.in +++ b/docs/Doxyfile.in @@ -2503,7 +2503,8 @@ DOT_NUM_THREADS = 0 # The default value is: fontname=Helvetica,fontsize=10. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_COMMON_ATTR = "fontname=HelveticaNeue,fontsize=10" +DOT_COMMON_ATTR = "fontname=lato,fontsize=10" +#DOT_COMMON_ATTR = "fontname=HelveticaNeue,fontsize=10" # DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can # add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Date: Wed, 3 Apr 2024 17:23:35 -0400 Subject: [PATCH 0581/2524] xo-unit: bugfix: quantity/quantity --- include/xo/unit/quantity.hpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index adb6cf80..9a86baba 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -123,8 +123,20 @@ namespace xo { using repr_type = std::common_type_t; - repr_type r_scale = ((scale() * c_scalefactor_inexact * unit_type::scalefactor_type::num) - / (y.scale() * unit_type::scalefactor_type::den)); + repr_type r_scale = ((scale() * c_scalefactor_inexact * exact_scalefactor_type::num) + / (y.scale() * exact_scalefactor_type::den)); + +# ifdef NOT_USING_DEBUG + using xo::reflect::Reflect; + scope log(XO_DEBUG(true /*c_debug_flag*/)); + log && log(xtag("unit_divide_type", Reflect::require()->canonical_name())); + log && log(xtag("exact_unit_type", Reflect::require()->canonical_name())); + log && log(xtag("norm_unit_type", Reflect::require()->canonical_name())); + log && log(xtag("exact_scalefactor_type", Reflect::require()->canonical_name())); + log && log(xtag("c_scalefactor_inexact", c_scalefactor_inexact)); + log && log(xtag("r_scale", r_scale)); + log && log(xtag("repr_type", Reflect::require()->canonical_name())); +# endif return quantity::promote(r_scale); } From bb5fc1cb4e5e4bbfc3b71a75d23a65fb2be93dfa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 17:23:56 -0400 Subject: [PATCH 0582/2524] xo-unit: quantity: + with_unit() + with_repr() convenience --- include/xo/unit/quantity.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 9a86baba..38fa44d8 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -77,6 +77,12 @@ namespace xo { return promoter::promote(x); } + template + constexpr quantity with_unit() const { return *this; } + + template + constexpr quantity with_repr() const { return quantity::promote(scale_); } + template auto multiply(Quantity2 y) const { //constexpr bool c_debug_flag = false; From 7c7373427ed6c980f78ff7e5eb874387a910e9d7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 17:24:55 -0400 Subject: [PATCH 0583/2524] xo-unit: refactor: c_basis_power<> -> basis_power<> --- example/ex1/CMakeLists.txt | 1 + example/ex2/CMakeLists.txt | 1 + example/ex3/CMakeLists.txt | 1 + example/ex3/ex3.cpp | 1 - include/xo/unit/quantity.hpp | 2 +- utest/quantity.test.cpp | 95 ++++++++++++++++++++++++------------ 6 files changed, 69 insertions(+), 32 deletions(-) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt index 0d415a06..679f538f 100644 --- a/example/ex1/CMakeLists.txt +++ b/example/ex1/CMakeLists.txt @@ -10,5 +10,6 @@ xo_include_options2(${SELF_EXE}) # dependencies.. xo_self_dependency(${SELF_EXE} xo_unit) +xo_dependency(${SELF_EXE} reflect) # end CMakeLists.txt diff --git a/example/ex2/CMakeLists.txt b/example/ex2/CMakeLists.txt index fa98d33b..e762d370 100644 --- a/example/ex2/CMakeLists.txt +++ b/example/ex2/CMakeLists.txt @@ -10,5 +10,6 @@ xo_include_options2(${SELF_EXE}) # dependencies.. xo_self_dependency(${SELF_EXE} xo_unit) +xo_dependency(${SELF_EXE} reflect) # end CMakeLists.txt diff --git a/example/ex3/CMakeLists.txt b/example/ex3/CMakeLists.txt index e6f40fc1..8f78979b 100644 --- a/example/ex3/CMakeLists.txt +++ b/example/ex3/CMakeLists.txt @@ -10,5 +10,6 @@ xo_include_options2(${SELF_EXE}) # dependencies.. xo_self_dependency(${SELF_EXE} xo_unit) +xo_dependency(${SELF_EXE} reflect) # end CMakeLists.txt diff --git a/example/ex3/ex3.cpp b/example/ex3/ex3.cpp index 77fcf03e..61a1d49c 100644 --- a/example/ex3/ex3.cpp +++ b/example/ex3/ex3.cpp @@ -7,7 +7,6 @@ int main () { namespace u = xo::unit::units; namespace qty = xo::unit::qty; - using xo::unit::quantity; using namespace std; auto t1 = qty::milliseconds(1); diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 38fa44d8..232583b4 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -63,7 +63,7 @@ namespace xo { * q.basis_power -> 0 **/ template - static constexpr PowerRepr c_basis_power = from_ratio::power_type>(); + static constexpr PowerRepr basis_power = from_ratio::power_type>(); /** @brief get scale value (relative to unit) (@ref scale_) **/ constexpr Repr scale() const { return scale_; } diff --git a/utest/quantity.test.cpp b/utest/quantity.test.cpp index 352ca2c5..75adba87 100644 --- a/utest/quantity.test.cpp +++ b/utest/quantity.test.cpp @@ -54,8 +54,8 @@ namespace xo { REQUIRE(t.scale() == 1); - static_assert(t.c_basis_power == 1); - static_assert(t.c_basis_power == 0); + static_assert(t.basis_power == 1); + static_assert(t.basis_power == 0); } /*TEST_CASE(quantity)*/ TEST_CASE("add1", "[quantity]") { @@ -74,8 +74,8 @@ namespace xo { CHECK(strcmp(sum.unit_cstr(), "s") == 0); static_assert(std::same_as); - static_assert(t1.c_basis_power == 1); - static_assert(t2.c_basis_power == 1); + static_assert(t1.basis_power == 1); + static_assert(t2.basis_power == 1); REQUIRE(sum.scale() == 3); @@ -95,7 +95,7 @@ namespace xo { auto m2 = minutes(2); { - static_assert(m2.c_basis_power == 1); + static_assert(m2.basis_power == 1); log && log(xtag("m2.scale", m2.scale()), xtag("m2.unit", m2.unit_cstr())); @@ -118,7 +118,7 @@ namespace xo { auto sum = t1 + t2; static_assert(std::same_as); - static_assert(sum.c_basis_power == 1); + static_assert(sum.basis_power == 1); log && log(xtag("t1.unit", t1.unit_cstr()), xtag("t2.unit", t2.unit_cstr())); log && log(xtag("sum.unit", sum.unit_cstr())); @@ -136,7 +136,7 @@ namespace xo { /* sum will take unit from lhs argument to + */ auto sum = t1 + t2; - static_assert(sum.c_basis_power == 1); + static_assert(sum.basis_power == 1); static_assert(std::same_as); REQUIRE(sum.scale() == 121); @@ -187,10 +187,10 @@ namespace xo { { q2 = q1; - static_assert(q1.c_basis_power == 1); - static_assert(q1.c_basis_power == -1); - static_assert(q2.c_basis_power == 1); - static_assert(q2.c_basis_power == -1); + static_assert(q1.basis_power == 1); + static_assert(q1.basis_power == -1); + static_assert(q2.basis_power == 1); + static_assert(q2.basis_power == -1); log && log(XTAG(q1), XTAG(q2)); @@ -249,7 +249,7 @@ namespace xo { { auto sum = vol_250d + vol_30d; - static_assert(sum.c_basis_power == -0.5); + static_assert(sum.basis_power == -0.5); log && log(XTAG(sum)); @@ -279,7 +279,7 @@ namespace xo { { auto r = q0 * q1; - static_assert(r.c_basis_power == 2); + static_assert(r.basis_power == 2); log && log(xtag("q0", q0), xtag("q1", q1), xtag("q0*q1", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); @@ -292,7 +292,7 @@ namespace xo { { auto r = q1 * q2; - static_assert(r.c_basis_power == 2); + static_assert(r.basis_power == 2); log && log(xtag("q1", q1), xtag("q2", q2), xtag("q1*q2", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); @@ -305,7 +305,7 @@ namespace xo { { auto r = q2 * q1; - static_assert(r.c_basis_power == 2); + static_assert(r.basis_power == 2); log && log(xtag("q1", q1), xtag("q2", q2), xtag("r=q2*q1", r)); log && log(xtag("r.type", Reflect::require()->canonical_name())); @@ -318,7 +318,7 @@ namespace xo { { auto r = q2 * 60; - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); static_assert(std::same_as); log && log(xtag("q2*60", r)); @@ -332,7 +332,7 @@ namespace xo { { auto r = q2 * 60U; - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); static_assert(std::same_as); log && log(xtag("q2*60U", r)); @@ -346,7 +346,7 @@ namespace xo { { auto r = (q2 * 60.5); - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); /* verify dimension */ static_assert(std::same_as); @@ -365,7 +365,7 @@ namespace xo { auto r = (q2 * 60.5f); /* verify dimension */ - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); static_assert(std::same_as); log && log(xtag("r.type", Reflect::require()->canonical_name())); @@ -378,7 +378,7 @@ namespace xo { { auto r = 60 * q2; - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); static_assert(std::same_as); log && log(xtag("60*q2", r)); @@ -394,7 +394,7 @@ namespace xo { auto r = 60.5 * q2; - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); log && log(xtag("r.type", Reflect::require()->canonical_name())); static_assert(std::same_as); @@ -409,7 +409,7 @@ namespace xo { auto r = 60.5f * q2; - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); static_assert(std::same_as); log && log(xtag("r.type", Reflect::require()->canonical_name())); @@ -475,7 +475,7 @@ namespace xo { log && log(XTAG(q0), xtag("r1=1.0/q0", r1)); /* verify dimension */ - static_assert(r1.c_basis_power == -1); + static_assert(r1.basis_power == -1); /* verify scale */ REQUIRE(r1.scale() == 0.2); @@ -529,7 +529,7 @@ namespace xo { log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ - static_assert(r.c_basis_power == -1); + static_assert(r.basis_power == -1); /* verify scale */ REQUIRE(r.scale() == 0.0125); @@ -542,7 +542,7 @@ namespace xo { log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); /* verify scale */ REQUIRE(r.scale() == 1.25); @@ -597,7 +597,7 @@ namespace xo { log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ - static_assert(r.c_basis_power == -1); + static_assert(r.basis_power == -1); /* verify scale */ REQUIRE(r.scale() == 0.0125); @@ -610,7 +610,7 @@ namespace xo { log && log(xtag("r.type", Reflect::require()->canonical_name())); /* verify dimension */ - static_assert(r.c_basis_power == 1); + static_assert(r.basis_power == 1); /* verify scale */ REQUIRE(r.scale() == 1.25); @@ -619,6 +619,8 @@ namespace xo { } /*TEST_CASE(div3)*/ TEST_CASE("div4", "[quantity]") { + /* test with exact scalefactor */ + constexpr bool c_debug_flag = false; // can get bits from /dev/random by uncommenting the 2nd line below @@ -630,6 +632,39 @@ namespace xo { scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div4")); //log && log("(A)", xtag("foo", foo)); + auto q1 = milliseconds(1); + auto q2 = minutes(1); + + auto r = q1 / q2.with_repr(); + + /* 0.1/sqrt(30dy) ~ 0.288675/sqrt(250dy), + * so q1/q2 ~ 0.6928 + */ + + log && log(XTAG(q1), XTAG(q2), xtag("q1/q2", r)); + + /* verify dimensionless result */ + static_assert(std::same_as); + + /* verify scale of result */ + CHECK(r == Approx(0.00001666667).epsilon(1e-6)); + + } /*TEST_CASE(div4)*/ + + TEST_CASE("div5", "[quantity]") { + /* test with inexact scalefactor */ + + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.div5")); + //log && log("(A)", xtag("foo", foo)); + auto q1 = volatility250d(0.2); auto q2 = volatility30d(0.1); @@ -647,10 +682,10 @@ namespace xo { /* verify scale of result */ CHECK(r == Approx(0.692820323).epsilon(1e-6)); - } /*TEST_CASE(div4)*/ + } /*TEST_CASE(div5)*/ TEST_CASE("muldiv5", "[quantity]") { - constexpr bool c_debug_flag = true; + constexpr bool c_debug_flag = false; // can get bits from /dev/random by uncommenting the 2nd line below //uint64_t seed = xxx; @@ -682,7 +717,7 @@ namespace xo { /* verify scale of result */ //CHECK(r == Approx(0.692820323).epsilon(1e-6)); - } /*TEST_CASE(div4)*/ + } /*TEST_CASE(muldiv5)*/ } /*namespace ut*/ } /*namespace xo*/ From ba31d2c1122ab9f4ab242497269f3134160f354f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 17:25:20 -0400 Subject: [PATCH 0584/2524] xo-unit: example: + ex4 .. ex6 --- example/CMakeLists.txt | 3 +++ example/ex4/CMakeLists.txt | 15 +++++++++++++++ example/ex4/ex4.cpp | 26 ++++++++++++++++++++++++++ example/ex5/CMakeLists.txt | 15 +++++++++++++++ example/ex5/ex5.cpp | 23 +++++++++++++++++++++++ example/ex6/CMakeLists.txt | 15 +++++++++++++++ example/ex6/ex6.cpp | 19 +++++++++++++++++++ 7 files changed, 116 insertions(+) create mode 100644 example/ex4/CMakeLists.txt create mode 100644 example/ex4/ex4.cpp create mode 100644 example/ex5/CMakeLists.txt create mode 100644 example/ex5/ex5.cpp create mode 100644 example/ex6/CMakeLists.txt create mode 100644 example/ex6/ex6.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index ed03faf2..86f0d7e6 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,3 +1,6 @@ add_subdirectory(ex1) add_subdirectory(ex2) add_subdirectory(ex3) +add_subdirectory(ex4) +add_subdirectory(ex5) +add_subdirectory(ex6) diff --git a/example/ex4/CMakeLists.txt b/example/ex4/CMakeLists.txt new file mode 100644 index 00000000..5e88f779 --- /dev/null +++ b/example/ex4/CMakeLists.txt @@ -0,0 +1,15 @@ +# xo-unit/example/ex4/CMakeLists.txt + +set(SELF_EXE xo_unit_ex4) +set(SELF_SRCS ex4.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_unit) +xo_dependency(${SELF_EXE} reflect) + +# end CMakeLists.txt diff --git a/example/ex4/ex4.cpp b/example/ex4/ex4.cpp new file mode 100644 index 00000000..6db7f145 --- /dev/null +++ b/example/ex4/ex4.cpp @@ -0,0 +1,26 @@ +/** @file ex4.cpp **/ + +#include "xo/unit/quantity.hpp" +#include + +int +main () { + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using namespace std; + + /* 20% volatility over 250 days (approx number of trading days in one year) */ + auto t1 = qty::milliseconds(1); + /* 10% volatility over 30 days */ + auto t2 = qty::minutes(1); + + auto r1 = t1 / t2.with_repr(); + auto r2 = t2 / t1.with_repr(); + + static_assert(same_as); + static_assert(same_as); + + cerr << "t1: " << t1 << ", t2: " << t2 << ", t1/t2: " << r1 << ", t2/t1: " << r2 << endl; +} + +/** end ex4.cpp */ diff --git a/example/ex5/CMakeLists.txt b/example/ex5/CMakeLists.txt new file mode 100644 index 00000000..583a7cff --- /dev/null +++ b/example/ex5/CMakeLists.txt @@ -0,0 +1,15 @@ +# xo-unit/example/ex5/CMakeLists.txt + +set(SELF_EXE xo_unit_ex5) +set(SELF_SRCS ex5.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_unit) +xo_dependency(${SELF_EXE} reflect) + +# end CMakeLists.txt diff --git a/example/ex5/ex5.cpp b/example/ex5/ex5.cpp new file mode 100644 index 00000000..2f0dbb9e --- /dev/null +++ b/example/ex5/ex5.cpp @@ -0,0 +1,23 @@ +/** @file ex5.cpp **/ + +#include "xo/unit/quantity.hpp" +#include + +int +main () { + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using namespace std; + + /* 20% volatility over 250 days (approx number of trading days in one year) */ + auto q1 = qty::volatility250d(0.2); + /* 10% volatility over 30 days */ + auto q2 = qty::volatility30d(0.1); + + auto sum = q1 + q2; + auto prod = q1 * q2; + + cerr << "q1: " << q1 << ", q2: " << q2 << ", q1+q2: " << sum << ", q1*q2: " << prod << endl; +} + +/** end ex5.cpp */ diff --git a/example/ex6/CMakeLists.txt b/example/ex6/CMakeLists.txt new file mode 100644 index 00000000..cff85d53 --- /dev/null +++ b/example/ex6/CMakeLists.txt @@ -0,0 +1,15 @@ +# xo-unit/example/ex6/CMakeLists.txt + +set(SELF_EXE xo_unit_ex6) +set(SELF_SRCS ex6.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_unit) +xo_dependency(${SELF_EXE} reflect) + +# end CMakeLists.txt diff --git a/example/ex6/ex6.cpp b/example/ex6/ex6.cpp new file mode 100644 index 00000000..9046f331 --- /dev/null +++ b/example/ex6/ex6.cpp @@ -0,0 +1,19 @@ +/** @file ex6.cpp **/ + +#include "xo/unit/quantity.hpp" +#include + +int +main () { + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using namespace std; + + auto t1 = qty::milliseconds(25.0); + auto t1_usec = t1.with_unit(); + auto t1_sec = t1.with_unit(); + + cerr << "t1: " << t1 << ", t1_usec: " << t1_usec << ", t1_sec: " << t1_sec << endl; +} + +/** end ex6.cpp */ From 715205f97b9c377daac80169aea180ba9d1490e9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 17:25:34 -0400 Subject: [PATCH 0585/2524] xo-unit: docs: + README --- docs/README | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 docs/README diff --git a/docs/README b/docs/README new file mode 100644 index 00000000..599096ac --- /dev/null +++ b/docs/README @@ -0,0 +1,30 @@ +map + + index.rst + +- install.rst + +- examples.rst + +- classes.rst + +- glossary.rst + +examples + +.. doxygenclass:: ${c++ class name} + :project: + :path: + :members: + :protected-members: + :private-members: + :undoc-members: + :member-groups: + :members-only: + :outline: + :no-link: + :allow-dot-graphs: + +.. doxygendefine:: ${c preprocessor define} + +.. doxygenconcept:: ${c++ concept definition} + +.. doxygenenum:: ${c++ enum definition} + +.. doxygenfunction:: ${c++ function name} From fdd80f9d5f1de906ed3fb19a44beeacda932f6e8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Apr 2024 17:25:55 -0400 Subject: [PATCH 0586/2524] xo-unit: docs: + written-out examples --- docs/examples.rst | 111 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/docs/examples.rst b/docs/examples.rst index 1aba11ed..d9beb387 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -46,6 +46,40 @@ Remarks: * Units are sticky: since we expressed ``t`` in milliseconds and ``m`` in kilograms, result is in the same terms. * Unit ordering is sticky. Mass appears on the left in printed value of ``a`` because it was on the left-hand side of ``operator/`` * Example omits verifying ``decltype(a)``, to keep output small. +* Conversion factors are exact (provided dimensions are limited to integer powers). + Exact conversion involves no loss of precision. + +Explicit scale conversion +------------------------- + +Can convert between compatible units explictly: + +.. code-block:: cpp + :linenos: + :emphasize-lines: 11-12 + + #include "xo/unit/quantity.hpp" + #include + + int + main () { + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using namespace std; + + auto t1 = qty::milliseconds(25.0); + auto t1_usec = t1.with_unit(); + auto t1_sec = t1.with_unit(); + + cerr << "t1: " << t1 << ", t1_usec: " << t1_usec << ", t1_sec: " << t1_sec << endl; + } + +with output: + +.. code-block:: + + t1: 25ms, t1_usec: 25000us, t1_sec: 0.025s + Scale conversion triggered by assignment ---------------------------------------- @@ -86,7 +120,7 @@ Remarks: Scale conversion triggered by arithmetic ---------------------------------------- -In representing a particular quantity, +When representing a particular quantity, xo-unit uses at most one scale for each :term:`basis dimension` associated with the unit for that quantity. When an arithmetic operator encounters basis units involving two different scales, the operator will adopt the scale provided by the left-hand argument: @@ -115,3 +149,78 @@ with output: .. code-block:: t1: 1ms, t2: 1min, t1*t2: 60000ms^2 + +Dimensionless quantities collapse automatically +----------------------------------------------- + +.. code-block:: cpp + :linenos: + :emphasize-lines: 14-15 + + #include "xo/unit/quantity.hpp" + #include + + int main() { + namespace u = xo::unit; + namespace qty = xo::units::qty; + using namespace std; + + auto t1 = qty::milliseconds(1); + auto t2 = qty::minutes(1); + auto r1 = t1 / t2.with_repr(); + auto r2 = t2 / t1.with_repr(); + + static_assert); + static_assert); + + cerr << "t1: " << t1 << ", t2: " << t2 << ", t1/t2: " << r1 << ", t2/t1: " << r2 << endl; + } + +with output: + +.. code-block:: + + t1: 1ms, t2: 1min, t1/t2: 1.66667e-05, t2/t1: 60000 + + +Fractional dimension +-------------------- + +Fractional dimensions are supported; they work in the same way as familiar integral dimensions. + +Only caveat is that converting between fractional units with different scales creates a floating-point conversion factor, +which may incur loss of precision based on floating-point roundoff. + +.. code-block:: cpp + :linenos: + :emphasize-lines: 15 + + #include "xo/unit/quantity.hpp" + #include + + int + main () { + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using namespace std; + + /* 20% volatility over 250 days (approx number of trading days in one year) */ + auto q1 = qty::volatility250d(0.2); + /* 10% volatility over 30 days */ + auto q2 = qty::volatility30d(0.1); + + static_assert(q2.basis_power == 0.5); + + auto sum = q1 + q2; + auto prod = q1 * q2; + + static_assert(prod.basis_power == 1); + + cerr << "q1: " << q1 << ", q2: " << q2 << ", q1+q2: " << sum << ", q1*q2" << prod << endl; + } + +with output: + +.. code-block:: + + q1: 0.2yr250^-(1/2), q2: 0.1mo^-(1/2), q1+q2: 0.488675yr250^(1/2), q1*q2: 0.057735yr250^-1 From 4d823595fed8c28a4b4c8d7975795ef7f6013c89 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:36:09 -0400 Subject: [PATCH 0587/2524] xo-unit: ex1: + static assert --- example/ex1/ex1.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 2e286e7c..8909e188 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -5,13 +5,17 @@ int main () { + namespace u = xo::unit::units; namespace qty = xo::unit::qty; + using xo::unit::quantity; using namespace std; auto t = qty::milliseconds(10); auto m = qty::kilograms(2.5); auto a = m / (t*t); + static_assert(same_as>); + cerr << "t: " << t << ", m: " << m << ", m.t^-2: " << a << endl; } From b4c3ba4dda9409887d04a100524d1dac899b9914 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:37:06 -0400 Subject: [PATCH 0588/2524] xo-unit: + numeric_concept + unit_concept --- include/xo/unit/numeric_concept.hpp | 38 +++++++++++++++++++++ include/xo/unit/quantity_concept.hpp | 3 +- include/xo/unit/unit_concept.hpp | 49 ++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 include/xo/unit/numeric_concept.hpp create mode 100644 include/xo/unit/unit_concept.hpp diff --git a/include/xo/unit/numeric_concept.hpp b/include/xo/unit/numeric_concept.hpp new file mode 100644 index 00000000..720099a3 --- /dev/null +++ b/include/xo/unit/numeric_concept.hpp @@ -0,0 +1,38 @@ +/* @file numeric_concept.hpp */ + +#pragma once + +#include + +namespace xo { + namespace unit { + /** @concept numeric_concept + * @brief Concept for values that participate in arithmetic operations (+,-,*,/) and comparisons + * + * Intended to include at least: + * - built-in integral and floating-point types + * - boost::rational + * - std::complex + * - xo::unit::quantity + * + * This implies we don't require T to be totally ordered, + * and don't require (<,<=,>=,>) operators. + * + * Intend numeric_concept to apply to types suitable for + * xo::unit::quantity::repr_type. + **/ + template + concept numeric_concept = requires(T x, U y) + { + { -x }; + { x - y }; + { x + y }; + { x * y }; + { x / y }; + { x == y }; + { x != y }; + }; + } /*namespace unit*/ +} /*namespace xo*/ + +/* end numeric_concept.hpp */ diff --git a/include/xo/unit/quantity_concept.hpp b/include/xo/unit/quantity_concept.hpp index 09d33446..1235a277 100644 --- a/include/xo/unit/quantity_concept.hpp +++ b/include/xo/unit/quantity_concept.hpp @@ -2,7 +2,8 @@ #pragma once -#include +#include "unit_concept.hpp" +#include "numeric_concept.hpp" namespace xo { namespace unit { diff --git a/include/xo/unit/unit_concept.hpp b/include/xo/unit/unit_concept.hpp new file mode 100644 index 00000000..8c5fe59c --- /dev/null +++ b/include/xo/unit/unit_concept.hpp @@ -0,0 +1,49 @@ +/* @file unit_concept.hpp */ + +#pragma once + +#include "dimension_concept.hpp" + +namespace xo { + namespace unit { + /** @brief concept for a Unit type, suitable for use with the quantity template + * + * Example: + * @code + * using namespace xo::unit; + * static_assert(unit_concept); + * @endcode + **/ + template + concept unit_concept = requires(Unit unit) + { + typename Unit::scalefactor_type; + typename Unit::dim_type; + typename Unit::canon_type; + } + && (ratio_concept + && bpu_list_concept + && bpu_list_concept); + + + /** @brief concept for a Unit type, that contains exactly one basis dimension + * + * Example: + * @code + * using namespace xo::unit + * static_assert(basis_unit_concept); + * @endcode + **/ + template + concept basis_unit_concept = requires(Unit unit) + { + typename Unit::dim_type; + typename Unit::dim_type::rest_type; + } + && (std::same_as) + && (unit_concept); + } /*namespace unit*/ +} /*namespace xo*/ + + +/* end unit_concept.hpp */ From 2cea0d3dfa896010d14280c9e40f61ef0ac789cd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:37:29 -0400 Subject: [PATCH 0589/2524] xo-unit: quantity_concept: + req concept sat for nested types --- include/xo/unit/quantity_concept.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/xo/unit/quantity_concept.hpp b/include/xo/unit/quantity_concept.hpp index 1235a277..1edb2c59 100644 --- a/include/xo/unit/quantity_concept.hpp +++ b/include/xo/unit/quantity_concept.hpp @@ -17,6 +17,7 @@ namespace xo { { Quantity::unit_cstr() } -> std::same_as; { Quantity::unit_quantity() } -> std::same_as; { Quantity::promote(repr) } -> std::same_as; - }; + } && (unit_concept + && numeric_concept); } /*namespace unit*/ } /*namespace xo*/ From 8fb3b11e000320df6c6f1157c1f34731eb0b6aa2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:38:09 -0400 Subject: [PATCH 0590/2524] xo-unit: removed unit_concept from dimension_concept.hpp --- include/xo/unit/dimension_concept.hpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/include/xo/unit/dimension_concept.hpp b/include/xo/unit/dimension_concept.hpp index 6277b621..c670c3e1 100644 --- a/include/xo/unit/dimension_concept.hpp +++ b/include/xo/unit/dimension_concept.hpp @@ -70,18 +70,6 @@ namespace xo { && bpu_list_concept ); - // ---------------------------------------------------------------- - - template - concept unit_concept = requires(Unit unit) - { - typename Unit::scalefactor_type; - typename Unit::dim_type; - typename Unit::canon_type; - } - && (ratio_concept - && bpu_list_concept - && bpu_list_concept); } /*namespace unit*/ } /*namespace xo*/ From 34214f571ddae67f7592eaf6a6f830a06433dd72 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:38:36 -0400 Subject: [PATCH 0591/2524] xo-unit: + di_replace_basis_scale --- include/xo/unit/dimension_impl.hpp | 44 +++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/include/xo/unit/dimension_impl.hpp b/include/xo/unit/dimension_impl.hpp index d51c655c..d241f9ec 100644 --- a/include/xo/unit/dimension_impl.hpp +++ b/include/xo/unit/dimension_impl.hpp @@ -180,7 +180,7 @@ namespace xo { using dim_type = typename Dim::rest_type; }; - // ----- bpu_list ----- + // ----- bpu_node ----- /** Represents the cartesian product of a list of 'native basis power units'; * represents something with dimensions @@ -234,6 +234,48 @@ namespace xo { static constexpr std::uint32_t n_dimension = 1; }; + // ----- di_replace_basis_scale ----- + + /** + * @brief Replace BpuList member with matching BasisDim, preserving everything except (inner) scalefactor + **/ + template + struct di_replace_basis_scale; + + template + struct di_replace_basis_scale_aux; + + /** specialization for non-empty BpuList **/ + template + struct di_replace_basis_scale { + using type = di_replace_basis_scale_aux::type; + }; + + /** specialization for empty BpuList -- error not found **/ + template + struct di_replace_basis_scale {}; + + /** specialization for matching front **/ + template + struct di_replace_basis_scale_aux { + using _replace_bpu_type = bpu; + + static_assert(native_bpu_concept<_replace_bpu_type>); + + /* NewBpu replaces Front */ + using type = bpu_node<_replace_bpu_type, Rest>; + }; + + template + struct di_replace_basis_scale_aux { + /* keep Front, replace NewBpu in rest */ + using type = bpu_node::type>; + }; + // ----- bpu_cartesian_product ----- /** Require: From fcdf0c8dfa6bf2c0f0e08bd2dab2c8b14a55b500 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:39:08 -0400 Subject: [PATCH 0592/2524] xo-unit: bugfix:fix meter, kilometer units --- include/xo/unit/unit.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/xo/unit/unit.hpp b/include/xo/unit/unit.hpp index b8b8c1b9..56157c94 100644 --- a/include/xo/unit/unit.hpp +++ b/include/xo/unit/unit.hpp @@ -270,12 +270,12 @@ namespace xo { }; using meter = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; + bpu_node< bpu> > >; using kilometer = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; + bpu_node< bpu> > >; template <> struct scaled_native_unit_abbrev> { static constexpr auto value = stringliteral("km"); From ccdaace50cb050b75490548114346a4f90482b4f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:39:41 -0400 Subject: [PATCH 0593/2524] xo-unit: unit.hpp: need unit_concept.hpp #incl --- include/xo/unit/unit.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/xo/unit/unit.hpp b/include/xo/unit/unit.hpp index 56157c94..d93ead8d 100644 --- a/include/xo/unit/unit.hpp +++ b/include/xo/unit/unit.hpp @@ -2,6 +2,7 @@ #pragma once +#include "unit_concept.hpp" #include "dimension_impl.hpp" namespace xo { From 859fdfd2c494bc0b60fc106b64fc29fac953b13d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:41:24 -0400 Subject: [PATCH 0594/2524] xo-unit: + unit_qty namespace w/ unit qty vars --- include/xo/unit/quantity.hpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 232583b4..457afd4e 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -513,6 +513,38 @@ namespace xo { return quantity::promote(x); } } /*namespace qty*/ + + namespace unit_qty { + /** @brief quantity with mass dimension, representing 1mg (1 milligram = 10^-3 grams) **/ + static constexpr auto milligram = qty::milligrams(1.0); + /** @brief quantity with mass dimension, representing 1g (1 gram) **/ + static constexpr auto gram = qty::grams(1.0); + /** @brief quantity with mass dimension, representing 1kg (1 kilogram = 1000 grams) **/ + static constexpr auto kilogram = qty::kilograms(1.0); + + /** @brief quantity with length dimension representing 1mm (10^-3 meters) **/ + static constexpr auto millimeter = qty::millimeters(1.0); + /** @brief quantity with length dimension representing 1m (1 meter) **/ + static constexpr auto meter = qty::meters(1.0); + /** @brief quantity with length dimension representing 1km (1 kilometer = 1000 meters) **/ + static constexpr auto kilometer = qty::kilometers(1.0); + + /** @brief quantity with time dimension representing 1ns (1 nanosecond = 10^-9 seconds) **/ + static constexpr auto nanosecond = qty::microseconds(1); + /** @brief quantity with time dimension representing 1us (1 microsecond = 10^-6 seconds) **/ + static constexpr auto microsecond = qty::microseconds(1); + /** @brief quantity with time dimension representing 1ms (1 milliseconds = 10^-3 seconds) **/ + static constexpr auto millisecond = qty::milliseconds(1); + /** @brief quantity with time dimension representing 1s (1 second) **/ + static constexpr auto second = qty::seconds(1); + /** @brief quantity with time dimension representing 1min (1 minute = 60 seconds) **/ + static constexpr auto minute = qty::minutes(1); + /** @brief quantity with time dimension representing 1hr (1 hour = 60 minutes) **/ + static constexpr auto hour = qty::hours(1); + /** @brief quantity with time dimension representing 1dy (1 day = 24 hours) **/ + static constexpr auto day = qty::days(1); + } + } /*namespace unit*/ } /*namespace xo*/ From 1a7321c98f0ae251eb6c8527258ad77ea51065fe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:42:26 -0400 Subject: [PATCH 0595/2524] xo-unit: quantity: ++ constexpr where appropriate --- include/xo/unit/quantity.hpp | 61 ++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 457afd4e..8085bcd5 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -286,7 +286,7 @@ namespace xo { // ----- operator+ ----- template - inline Quantity1 operator+ (Quantity1 x, Quantity2 y) { + inline constexpr Quantity1 operator+ (Quantity1 x, Quantity2 y) { static_assert(same_dimension_v); /* convert y to match units used by x; @@ -298,7 +298,7 @@ namespace xo { } template - inline Quantity1 operator- (Quantity1 x, Quantity2 y) { + inline constexpr Quantity1 operator- (Quantity1 x, Quantity2 y) { static_assert(std::same_as < typename Quantity1::unit_type::dimension_type::canon_type, typename Quantity2::unit_type::dimension_type::canon_type >); @@ -315,7 +315,7 @@ namespace xo { } template - inline auto operator* (Quantity1 x, Quantity2 y) { + inline constexpr auto operator* (Quantity1 x, Quantity2 y) { static_assert(quantity_concept); static_assert(quantity_concept); @@ -325,7 +325,7 @@ namespace xo { /** e.g. DECLARE_LH_MULT(int32_t) **/ # define DECLARE_LH_MULT(lhtype) \ template \ - inline auto \ + inline constexpr auto \ operator* (lhtype x, Quantity y) { \ static_assert(quantity_concept); \ return y.scale_by(x); \ @@ -346,7 +346,7 @@ namespace xo { /** e.g. DECLARE_RH_MULT(int32_t) **/ # define DECLARE_RH_MULT(rhtype) \ template \ - inline auto \ + inline constexpr auto \ operator* (Quantity x, rhtype y) { \ static_assert(quantity_concept); \ return x.scale_by(y); \ @@ -365,7 +365,7 @@ namespace xo { # undef DECLARE_LH_MULT template - inline auto operator/ (Quantity1 x, Quantity2 y) { + inline constexpr auto operator/ (Quantity1 x, Quantity2 y) { static_assert(quantity_concept); static_assert(quantity_concept); @@ -374,7 +374,7 @@ namespace xo { # define DECLARE_LH_DIV(lhtype) \ template \ - inline auto \ + inline constexpr auto \ operator/ (lhtype x, Quantity y) { \ static_assert(quantity_concept); \ return y.divide_into(x); \ @@ -394,7 +394,7 @@ namespace xo { # define DECLARE_RH_DIV(rhtype) \ template \ - inline auto \ + inline constexpr auto \ operator/ (Quantity x, rhtype y) { \ static_assert(quantity_concept); \ return x.divide_by(y); \ @@ -422,72 +422,85 @@ namespace xo { namespace qty { // ----- mass ----- + /** @brief create quantity representing @p x milligrams **/ template - inline auto milligrams(Repr x) -> quantity { + inline constexpr auto milligrams(Repr x) -> quantity { return quantity::promote(x); }; + /** @brief create quantity representing @p x grams **/ template - inline auto grams(Repr x) -> quantity { + inline constexpr auto grams(Repr x) -> quantity { return quantity::promote(x); }; + /** @brief create quantity representing @p x kilograms **/ template - inline auto kilograms(Repr x) -> quantity { + inline constexpr auto kilograms(Repr x) -> quantity { return quantity::promote(x); }; // ----- distance ----- + /** @brief create quantity representing @p x millimeters **/ template - inline auto millimeters(Repr x) -> quantity { + inline constexpr auto millimeters(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x meters **/ template - inline auto meters(Repr x) -> quantity { + inline constexpr auto meters(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x kilometers **/ template - inline auto kilometers(Repr x) -> quantity { + inline constexpr auto kilometers(Repr x) -> quantity { return quantity::promote(x); } // ----- time ----- + /** @brief create quantity representing @p x nanoseconds **/ template - inline auto nanoseconds(Repr x) -> quantity { + inline constexpr auto nanoseconds(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x microseconds **/ template - inline auto microseconds(Repr x) -> quantity { + inline constexpr auto microseconds(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x milliseconds **/ template - inline auto milliseconds(Repr x) -> quantity { + inline constexpr auto milliseconds(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x seconds **/ template - inline auto seconds(Repr x) -> quantity { + inline constexpr auto seconds(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x minutes **/ template - inline auto minutes(Repr x) -> quantity { + inline constexpr auto minutes(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x hours **/ template - inline auto hours(Repr x) -> quantity { + inline constexpr auto hours(Repr x) -> quantity { return quantity::promote(x); } + /** @brief create quantity representing @p x days (1 day = exactly 24 hours) **/ template - inline auto days(Repr x) -> quantity { + inline constexpr auto days(Repr x) -> quantity { return quantity::promote(x); } @@ -495,21 +508,21 @@ namespace xo { /** quantity in units of 1/sqrt(1dy) **/ template - inline auto volatility1d(Repr x) -> quantity { + inline constexpr auto volatility1d(Repr x) -> quantity { return quantity::promote(x); } /** quantity in units of 1/sqrt(30days) **/ template - inline auto volatility30d(Repr x) -> quantity { + inline constexpr auto volatility30d(Repr x) -> quantity { return quantity::promote(x); } /** quantity in units of 1/sqrt(250days) **/ template - inline auto volatility250d(Repr x) -> quantity { + inline constexpr auto volatility250d(Repr x) -> quantity { return quantity::promote(x); } } /*namespace qty*/ From 0c355d3e8b57b0e7d26195e36f3e2ec70daf0f55 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:43:52 -0400 Subject: [PATCH 0596/2524] xo-unit: ++ doxygen decors on quantity members --- include/xo/unit/quantity.hpp | 352 +++++++++++++++++++++++++++++------ include/xo/unit/unit.hpp | 1 + 2 files changed, 291 insertions(+), 62 deletions(-) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 8085bcd5..bab5655c 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -20,26 +20,27 @@ namespace xo { /** @class quantity * - * @brief represets a scalar quantity; enforces dimensional consistency at compile time + * @brief represents a scalar quantity; enforces dimensional consistency at compile time. * - * Repr representation. - * Unit unit - * Assert use to specify required unit dimension + * - @p Unit is a type identifying dimension and scale attaching to this quantity. + * Unit must satisfy @c unit_concept + * - @p Repr is a type used to represent quantity values, scaled by @p Unit. + * Repr must satisfy @c numeric_concept * - * Require: - * - Repr copyable, assignable - * - Repr = 0 - * - Repr = 1 - * - Repr + Repr -> Repr - * - Repr - Repr -> Repr - * - Repr * Repr -> Repr - * - Repr / Repr -> Repr + * A quantity's run-time state consists of exactly one @p Repr + * instance: + * @c sizeof(quantity) == sizeof(Repr) **/ template class quantity { public: + /** @defgroup quantity-traits **/ + ///@{ + /** @brief type capturing the units (and dimension) of this quantity **/ using unit_type = Unit; + /** @brief type used for representation of this quantity **/ using repr_type = Repr; + ///@} /* non-unity compile-time scale factors can arise during unit conversion; * for example see method quantity::in_units_of() @@ -48,41 +49,232 @@ namespace xo { static_assert(std::same_as< typename Unit::canon_type, typename Unit::canon_type >); public: + /** @defgroup quantity-ctors constructors + **/ + ///@{ constexpr quantity() = default; constexpr quantity(quantity const & x) = default; constexpr quantity(quantity && x) = default; + ///@} - template - using find_bpu_t = unit_find_bpu_t; - - /** - * For example: - * auto q = qty::milliseconds(5) * qty::seconds(1); - * then - * q.basis_power -> 2 - * q.basis_power -> 0 + /** @defgroup quantity-named-ctors named constructors + **/ + ///@{ + /** @brief construct a unit quantity using @c unit_type + * + * @code + * auto q = qty::milliseconds(17) / qty::kilometers(23.0); + * q::unit_quantity(); // 1ms.km^-1 + * @endcode **/ - template - static constexpr PowerRepr basis_power = from_ratio::power_type>(); - - /** @brief get scale value (relative to unit) (@ref scale_) **/ - constexpr Repr scale() const { return scale_; } - /** @brief abbreviation for this quantity's units **/ - static constexpr char const * unit_cstr() { return unit_abbrev_v.c_str(); } - /** @brief return unit quantity -- amount with this Unit that has representation = 1 **/ static constexpr quantity unit_quantity() { return quantity(1); } - /** @brief promote representation to quantity. Same as multiplying by Unit **/ + /** @brief promote representation to quantity. Same as multiplying by Unit + **/ static constexpr auto promote(Repr x) { //std::cerr << "quantity::promote: x=" << x << ", R=" << reflect::Reflect::require()->canonical_name() << std::endl; return promoter::promote(x); } + ///@} - template - constexpr quantity with_unit() const { return *this; } + /** @addtogroup quantity-traits **/ + ///@{ + /** @brief report this quantity's basis-power-unit type for a given basis dimension + * + * Example: + * @code + * auto q = 1.0 / (qty::milliseconds(5) * qty::seconds(100.0)); + * q.unit_cstr(); // "ms^-2" + * + * using tmp = q.find_bpu_t; + * + * tmp::c_native_dim; // dim::time + * tmp::c_native_unit; // native_unit_id::second + * tmp::scalefactor_type::num; // 1 + * tmp::scalefactor_type::den; // 1000 + * tmp::power_type::num; // -2 + * tmp::pwoer_type::den; // 1 + * @endcode + **/ + template + using find_bpu_t = unit_find_bpu_t; + + /** @brief report this quantity's scalefactor type for given basis dimension **/ + template + using basis_scale_type = typename find_bpu_t::scalefactor_type::type; + + ///@} + + /** @defgroup quantity-access-methods **/ + ///@{ + /** @brief get scale value (relative to unit) (@ref scale_) **/ + constexpr Repr scale() const { return scale_; } + /** @brief abbreviation for this quantity's units + * + * This string literal is constructed at compile-time by concatenating + * abbreviations for each basis-power-unit. + * For implementation see: + * * @c xo::unit::native_unit_abbrev_helper + * (in xo/unit/basis_unit.hpp) for each native dimension + * * @c xo::unit::scaled_native_unit_abbrev + * (in xo/unit/basis_unit.hpp) last-resort handling for scaled native dimensions + * * @c xo::unit::scaled_native_unit_abbrev + * (in xo/unit/unit.hpp) specializations for scaled native dimensions + **/ + static constexpr char const * unit_cstr() { return unit_abbrev_v.c_str(); } + ///@} + + /** @defgroup quantity-constants constants + **/ + ///@{ + /** @brief report exponent of @p BasisDim in dimension of this quantity + * + * For example: + * @code + * auto q = qty::milliseconds(5) * qty::seconds(1); + * int p1 = q.basis_power; // p1 == 2 + * int p2 = q.basis_power; // p2 == 0 + * @endcode + **/ + template + static constexpr PowerRepr basis_power = from_ratio::power_type>(); + ///@} + + /** @defgroup quantity-unit-conversion **/ + ///@{ + /** @brief convert to quantity representing the same amount, but changing units and perhaps representation. + * + * These two expressions are equivalent: + * @code + * q.with_unit(); + * quantity(q); + * @endcode + * + **/ + template + constexpr quantity with_unit() const { return *this; } + + /** + * @brief produce quantity scaled according to @p BasisUnit2, representing the same value as @c *this. + * + * For example: + * + * @code{.cpp} + * auto q1 = 1.0 / minutes(1) * kilograms(2.5); // q1 = 2.5kg.min^-1 + * auto q2 = q1.with_basis_unit(); // q2 in kg.ms^-1 + * @endcode + * + * Motivation is ability to chain rescaling to reach desired compound unit + * + * @code + * auto q3 = q1.with_basis_unit() + * .with_basis_unit(); // q3 in g.s^-1 + * @endcode + **/ + template + constexpr auto with_basis_unit() const { + static_assert(basis_unit_concept); + + using new_bpu_type = BasisUnit2::dim_type::front_type; + using old_bpulist_type = unit_type::dim_type; + using new_bpulist_type = di_replace_basis_scale::type; + using new_unit_type = wrap_unit, new_bpulist_type>; + +# ifdef NOT_USING_DEBUG + using xo::reflect::Reflect; + scope log(XO_DEBUG(true /*c_debug_flag*/)); + log && log(xtag("old_unit_type", Reflect::require()->canonical_name())); + log && log(xtag("new_unit_type", Reflect::require()->canonical_name())); +# endif + + return this->with_unit(); + } + + /** + * @brief express this quantity in the same units as @p q + * + * @pre @c *this and @p q must have the same dimension + * + * @param q take units from @c q::unit_type, ignoring @c q.scale() + * @return this amount, but expressed using the same units as @p q + **/ + template + auto with_units_from(Quantity q) const { + return this->with_units(); + } + + /** + * @brief express this quantity in units of @p Unit2. + * + * @p Unit2 specifies new units + * @p Repr2 specifies representation + * @return this amount, but expressed as a multiple of @p Unit2 + **/ + template + auto with_units() const { + Repr2 x = this->in_units_of(); + + return quantity::promote(x); + } + + /** + * @brief compute scale with respect to @p Unit2 + * + * @pre @c *this must have the same dimension as @p Unit2 + * + * @p Unit2 rescale in terms of this unit. + * @p Repr2 compute scale in this representation + * @return scale to use for @c quantity representing the same amount as @c *this. + **/ + template + Repr2 in_units_of() const { + // static_assert(dimension_of == dimension_of); // discard all the scaling values + + static_assert(same_dimension_v); + + using _convert_to_u2_type = unit_cartesian_product>; + + using exact_scalefactor_type = _convert_to_u2_type::exact_unit_type::scalefactor_type; + constexpr double c_scalefactor_inexact = _convert_to_u2_type::c_scalefactor_inexact; + + // _convert_u2_type + // - scalefactor_type + // - dim_type + // - canon_type + + /* if _convert_u2_type isn't dimensionless, then {Unit2, Unit} have different dimensions */ + + return ((this->scale_ * c_scalefactor_inexact * exact_scalefactor_type::num) / exact_scalefactor_type::den); + } + + /** + * @brief convert to quantity with representation @p Repr2 + * + * @return a quantity representing the same amount as @c *this, but using representation @p Repr2 + **/ template constexpr quantity with_repr() const { return quantity::promote(scale_); } + ///@} + + /** @defgroup quantity-arithmeticsupport **/ + ///@{ + /** + * @brief multiply this quantity *x* by another quantity *y*. + * + * Result will propagate dimension and units appropriately. + * If *x* and *y* use conflicting scale factors for a dimension, + * adopt scalefactor from *x*. + * + * note: result will be a dimensionless value (e.g. type @c double) + * if units cancel. + * + * @pre @p Quantity2 must satisfy @c quantity_concept + * + * @param y multiply by this amount + * @return x.multiply(y) returns amount representing x*y + **/ template auto multiply(Quantity2 y) const { //constexpr bool c_debug_flag = false; @@ -118,6 +310,21 @@ namespace xo { return quantity::promote(r_scale); } + /** + * @brief multiply this quantity *x* by another quantity *y* + * + * Result will propagate dimension and units appropriately. + * If *x* and *y* use conflicting scale factors for a dimension, + * adopt scalefactor from *x*. + * + * note: result will be a dimensionless value (e.g. type @c double) + * if units cancel. + * + * @pre @p Quantity2 must satisfy @c quantity_concept + * + * @param y divide by this amount + * @return x.divide(y) returns amount representing x/y + **/ template auto divide(Quantity2 y) const { using unit_divide_type = unit_divide; @@ -151,15 +358,17 @@ namespace xo { // quantity operator/=() /** - * scale by dimensionless number + * @brief scale this quantity *x* by dimensionless amount @p y + * + * @return quantity representing @c x*y **/ template - auto scale_by(Repr2 x) const { + auto scale_by(Repr2 y) const { static_assert(!quantity_concept); using r_repr_type = std::common_type_t; - r_repr_type r_scale = this->scale_ * x; + r_repr_type r_scale = this->scale_ * y; //std::cerr << "quantity::scale_by: scale=" << scale << ", repr_type=" << reflect::Reflect::require()->canonical_name() << std::endl; @@ -167,7 +376,9 @@ namespace xo { } /** - * divide by dimensionless number + * @brief divide this quantity *x* by dimensionless amount @p y + * + * @return quantity representing @c x/y **/ template auto divide_by(Repr2 x) const { @@ -179,7 +390,9 @@ namespace xo { } /** - * divide dimensionless number by this quantity + * @brief divide dimensionless number @p x by this quantity @c y + * + * @return quantity representing @c x/y **/ template auto divide_into(Repr2 x) const { @@ -191,30 +404,19 @@ namespace xo { return quantity::promote(r_scale); } + ///@} - template - Repr2 in_units_of() const { - // static_assert(dimension_of == dimension_of); // discard all the scaling values - - static_assert(same_dimension_v); - - using _convert_to_u2_type = unit_cartesian_product>; - - using exact_scalefactor_type = _convert_to_u2_type::exact_unit_type::scalefactor_type; - constexpr double c_scalefactor_inexact = _convert_to_u2_type::c_scalefactor_inexact; - - // _convert_u2_type - // - scalefactor_type - // - dim_type - // - canon_type - - /* if _convert_u2_type isn't dimensionless, then {Unit2, Unit} have different dimensions */ - - return ((this->scale_ * c_scalefactor_inexact * exact_scalefactor_type::num) / exact_scalefactor_type::den); - } - + /** @defgroup quantity-arithmetic **/ + ///@{ + /** @brief add quantity in-place + * + * @pre @p y must have the same dimension as @c *this. + * + * @param y quantity to add + * @retval this quantity after adding y + **/ template - quantity operator+=(Quantity2 y) { + quantity & operator+=(Quantity2 y) { static_assert(std::same_as< typename unit_type::canon_type, typename Quantity2::unit_type::canon_type >); @@ -227,8 +429,15 @@ namespace xo { return *this; } + /** @brief subtract quantity in-place + * + * @pre @p y must have the same dimensions as @c *this + * + * @param y quantity to subtract + * @retval this quantity after subtracting y + **/ template - quantity operator-=(Quantity2 y) { + quantity & operator-=(Quantity2 y) { static_assert(std::same_as< typename unit_type::canon_type, typename Quantity2::unit_type::canon_type >); @@ -240,10 +449,16 @@ namespace xo { return *this; } + ///@} - /* convert to quantity with same dimension, different {unit_type, repr_type} */ + /** @addtogroup quantity-unit-conversion **/ + ///@{ + /** @brief convert to quantity with same dimension, different {unit_type, repr_type} + * + * @pre @c Quantity2 must have the same dimension as @c *this. + **/ template - operator Quantity2 () const { + constexpr operator Quantity2 () const { /* avoid truncating precision when converting: * use best available representation */ @@ -251,13 +466,26 @@ namespace xo { return Quantity2::promote(this->in_units_of()); } + ///@} + /** @defgroup quantity-print-support **/ + ///@{ + /** @brief write printed representation on stream + * + * @param os write on this output stream + **/ void display(std::ostream & os) const { os << this->scale() << unit_cstr(); } + ///@} + /** @defgroup quantity-assignment **/ + ///@{ + /** @brief copy constructor **/ quantity & operator=(quantity const & x) = default; + /** @brief move constructor **/ quantity & operator=(quantity && x) = default; + ///@} private: explicit constexpr quantity(Repr x) : scale_{x} {} diff --git a/include/xo/unit/unit.hpp b/include/xo/unit/unit.hpp index d93ead8d..54c8fffc 100644 --- a/include/xo/unit/unit.hpp +++ b/include/xo/unit/unit.hpp @@ -239,6 +239,7 @@ namespace xo { // ----- weight ----- + /** @brief a unit type representing 1mg (10^-3 grams) **/ using milligram = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; From 0ff7b7dc11e775ac83cd33e796d9884d3a8e5a70 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:44:19 -0400 Subject: [PATCH 0597/2524] xo-unit: quantity: check template args sat concepts --- include/xo/unit/quantity.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index bab5655c..f8d5dd80 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -42,6 +42,8 @@ namespace xo { using repr_type = Repr; ///@} + static_assert(unit_concept); + static_assert(numeric_concept); /* non-unity compile-time scale factors can arise during unit conversion; * for example see method quantity::in_units_of() */ From 1740a232945eb45b4cb6cbdde839991561d53f34 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:44:44 -0400 Subject: [PATCH 0598/2524] xo-unit: utest: + quantity rescale tests --- utest/quantity.test.cpp | 71 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/utest/quantity.test.cpp b/utest/quantity.test.cpp index 75adba87..83cf18ce 100644 --- a/utest/quantity.test.cpp +++ b/utest/quantity.test.cpp @@ -12,9 +12,14 @@ namespace xo { using xo::unit::quantity; using xo::unit::qty::kilograms; + + using xo::unit::qty::meters; + using xo::unit::qty::kilometers; + using xo::unit::qty::milliseconds; using xo::unit::qty::seconds; using xo::unit::qty::minutes; + using xo::unit::qty::hours; using xo::unit::qty::volatility30d; using xo::unit::qty::volatility250d; @@ -718,6 +723,72 @@ namespace xo { //CHECK(r == Approx(0.692820323).epsilon(1e-6)); } /*TEST_CASE(muldiv5)*/ + + TEST_CASE("rescale", "[quantity]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.rescale")); + //log && log("(A)", xtag("foo", foo)); + + auto q = kilograms(150.0) / minutes(1); /* 150.0kg.min^-1 */ + + CHECK(strcmp(q.unit_cstr(), "kg.min^-1") == 0); + CHECK(q.scale() == 150.0); + + log && log(XTAG(q)); + + namespace u = xo::unit::units; + + auto q1 = q.with_basis_unit(); /* 0.0025kg.ms^-1 */ + + CHECK(strcmp(q1.unit_cstr(), "kg.ms^-1") == 0); + CHECK(q1.scale() == 0.0025); + + log && log(XTAG(q1)); + + auto q2 = q1.with_basis_unit(); /* 2.5g.ms^-1 */ + + CHECK(strcmp(q2.unit_cstr(), "g.ms^-1") == 0); + CHECK(q2.scale() == 2.5); + + log && log(XTAG(q2)); + + auto q3 = q2.with_basis_unit(); /* 2500g.s^-1 */ + + CHECK(strcmp(q3.unit_cstr(), "g.s^-1") == 0); + CHECK(q3.scale() == 2500.0); + + log && log(XTAG(q3)); + } /*TEST_CASE(rescale)*/ + + TEST_CASE("rescale2", "[quantity]") { + constexpr bool c_debug_flag = true; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.rescale2")); + //log && log("(A)", xtag("foo", foo)); + + namespace u = xo::unit::unit_qty; + + auto q1 = kilometers(150.0) / u::hour; + auto q2 = q1.with_units_from(u::meter / u::second); + + log && log(XTAG(q1), XTAG(q2)); + } /*TEST_CASE(rescale2)*/ + + + } /*namespace ut*/ } /*namespace xo*/ From c25930b7f50179ac1f54370a1ef8c9258e999c7d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 01:45:17 -0400 Subject: [PATCH 0599/2524] xo-unit: docs: cmakefile deps ++ cover quantity features --- docs/CMakeLists.txt | 22 ++++--- docs/README | 1 + docs/classes.rst | 10 ---- docs/examples.rst | 12 ++-- docs/index.rst | 8 +-- docs/quantity-class.rst | 70 ++++++++++++++++++++++ docs/quantity-factoryfunctions.rst | 27 +++++++++ docs/quantity-reference.rst | 12 ++++ docs/quantity-unitvars.rst | 45 +++++++++++++++ docs/unit-concept.rst | 12 ++++ docs/unit-quantities.rst | 93 ++++++++++++++++++++++++++++++ docs/unit-reference.rst | 10 ++++ 12 files changed, 296 insertions(+), 26 deletions(-) delete mode 100644 docs/classes.rst create mode 100644 docs/quantity-class.rst create mode 100644 docs/quantity-factoryfunctions.rst create mode 100644 docs/quantity-reference.rst create mode 100644 docs/quantity-unitvars.rst create mode 100644 docs/unit-concept.rst create mode 100644 docs/unit-quantities.rst create mode 100644 docs/unit-reference.rst diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 8c7b9b92..35be8468 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -7,7 +7,7 @@ else() # otherwise use top-level doxygen setup instead. set(ALL_LIBRARY_TARGETS xo_unit) # todo: automate this from xo-cmake macros - set(ALL_UTEST_TARGETS utest.unit) # todo: automate this from xo-cmake macros + set(ALL_UTEST_TARGETS utest.unit xo_unit_ex1 xo_unit_ex2 xo_unit_ex3 xo_unit_ex4 xo_unit_ex5 xo_unit_ex6) # todo: automate this from xo-cmake macros # look for doxygen executable find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED) @@ -19,22 +19,27 @@ else() set(DOX_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) - #set(DOX_DEPS ${PROJECT_SOURCE_DIR}/mainpage.dox) # not yet set(DOX_INPUT_DIR ${PROJECT_SOURCE_DIR}) set(DOX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dox) set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html) + # .hpp files reachable from xo-unit/include + # + # REMINDER: for reliability will need to re-run cmake when the set of .hpp files changes + # + file(GLOB_RECURSE DOX_HPP_FILES_GLOB ${PROJECT_SOURCE_DIR}/include *.hpp) + set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/sphinx/html) set(SPHINX_INDEX_FILE ${SPHINX_OUTPUT_DIR}/index.html) # # sphinx .rst files reachable from cmake-examples/docs # - # REMINDER: will need to re-run cmake when the set of .rst files changes + # REMINDER: for reliability will need to re-run cmake when the set of .rst files changes # file(GLOB_RECURSE SPHINX_RST_FILES_GLOB ${CMAKE_CURRENT_SOURCE_DIR} *.rst) - set(SPHINX_RST_FILES index.rst install.rst examples.rst classes.rst glossary.rst) + set(SPHINX_RST_FILES index.rst install.rst examples.rst unit-quantities.rst quantity-class.rst quantity-factoryfunctions.rst quantity-unitvars.rst unit-reference.rst unit-concept.rst glossary.rst) # TODO: # 1. move Doxyfile.in to xo-cmake project @@ -45,10 +50,12 @@ else() FILE_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE @ONLY) + set(DOX_DEPS ${ALL_LIBRARY_TARGETS} ${ALL_UTEST_TARGETS} ${DOX_HPP_FILES_GLOB}) + file(MAKE_DIRECTORY ${DOX_OUTPUT_DIR}) add_custom_command( OUTPUT ${DOX_INDEX_FILE} - DEPENDS ${DOX_DEPS} ${ALL_LIBRARY_TARGETS} ${ALL_UTEST_TARGETS} + DEPENDS ${DOX_DEPS} COMMAND "${DOXYGEN_EXECUTABLE}" ${DOX_CONFIG_FILE} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} MAIN_DEPENDENCY ${DOX_CONFIG_FILE} @@ -62,15 +69,16 @@ else() # add_custom_target( doxygen - DEPENDS ${DOX_INDEX_FILE} + DEPENDS ${DOX_INDEX_FILE} ${DOX_DEPS} ) # root of sphinx doc tree set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) + set(SPHINX_DEPS doxygen conf.py ${SPHINX_RST_FILES} ${SPHINX_RST_FILES_GLOB} ${DOX_DEPS}) add_custom_command( OUTPUT ${SPHINX_INDEX_FILE} - DEPENDS doxygen conf.py ${SPHINX_RST_FILES} ${SPHINX_RST_FILES_GLOB} + DEPENDS ${SPHINX_DEPS} COMMAND ${SPHINX_EXECUTABLE} -b html -Dbreathe_projects.xodoxxml=${CMAKE_CURRENT_BINARY_DIR}/dox/xml ${SPHINX_SOURCE} ${SPHINX_OUTPUT_DIR} diff --git a/docs/README b/docs/README index 599096ac..078db7c8 100644 --- a/docs/README +++ b/docs/README @@ -3,6 +3,7 @@ map index.rst +- install.rst +- examples.rst + +- unit-quantities.rst +- classes.rst +- glossary.rst diff --git a/docs/classes.rst b/docs/classes.rst deleted file mode 100644 index 1e3ac342..00000000 --- a/docs/classes.rst +++ /dev/null @@ -1,10 +0,0 @@ -.. _classes: - -.. toctree - :maxdepth: 2 - -Template Classes -================ - -.. doxygenclass:: xo::unit::quantity - :members: diff --git a/docs/examples.rst b/docs/examples.rst index d9beb387..f9ddda45 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -15,16 +15,17 @@ Compile-time unit inference #include int main() { - namespace u = xo::unit; - namespace qty = u::qty; + namespace u = xo::unit::units; + namespace qty = xo::unit::qty; + using xo::unit::quantity; using namespace std; auto t = qty::milliseconds(10); auto m = qty::kilograms(2.5); auto a = m / (t * t); - static_assert(same_as>); - static_assert(same_as>); + static_assert(same_as>); + static_assert(same_as>); static_assert(sizeof(t) == sizeof(int)); static_assert(sizeof(m) == sizeof(double)); static_assert(sizeof(a) == sizeof(double)); @@ -88,7 +89,7 @@ One way to convert units is by assignment: .. code-block:: cpp :linenos: - :emphasize-lines: 9-10 + :emphasize-lines: 10-11 #include "xo/unit/quantity.hpp" #include @@ -96,6 +97,7 @@ One way to convert units is by assignment: int main() { namespace u = xo::unit; namespace qty = xo::units::qty; + using xo::unit::quantity; using namespace std; quantity t = qty::milliseconds(10); diff --git a/docs/index.rst b/docs/index.rst index 108d97d4..12ffda2e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,11 +24,13 @@ runtime (since we can't construct new c++ types at runtime). .. toctree:: :maxdepth: 2 - :caption: Contents: + :caption: xo-unit contents: install examples - classes + unit-quantities + quantity-reference + unit-reference Indices and Tables ------------------ @@ -36,5 +38,3 @@ Indices and Tables * :ref:`glossary` * :ref:`genindex` * :ref:`search` - -.. toctree:: diff --git a/docs/quantity-class.rst b/docs/quantity-class.rst new file mode 100644 index 00000000..2945026e --- /dev/null +++ b/docs/quantity-class.rst @@ -0,0 +1,70 @@ +.. _quantity-class: + +Quantity +======== + +.. code-block:: cpp + + #include + +The primary data structure for interacting with xo-unit is the +template class ``xo::unit::quantity``. + +.. doxygenclass:: xo::unit::quantity + +Type Traits +----------- + +.. doxygengroup:: quantity-traits + :content-only: + +Constructors +------------ + +.. doxygengroup:: quantity-ctors + :content-only: + +.. doxygengroup:: quantity-named-ctors + :content-only: + +The simplest way to create a quantity instance is to use either + +* factory functions in ``xo::unit::qty``, see :doc:`quantity-factoryfunctions` +* unit variables in ``xo::unit::units``, see :doc:`quantity-unitvars` + +Access Methods +-------------- + +.. doxygengroup:: quantity-access-methods + :content-only: + +Constants +--------- + +.. doxygengroup:: quantity-constants + :content-only: + +Conversion Methods +------------------ + +Amount-preserving conversion to quantities with different units and/or representation. + +.. doxygengroup:: quantity-unit-conversion + :content-only: + +Arithmetic +---------- + +.. doxygengroup:: quantity-arithmetic + :content-only: + +Support methods for arithmetic operations + +.. doxygengroup:: quantity-arithmeticsupport + :content-only: + +Assignment +---------- + +.. doxygengroup:: quantity-assignment + :content-only: diff --git a/docs/quantity-factoryfunctions.rst b/docs/quantity-factoryfunctions.rst new file mode 100644 index 00000000..f73d9745 --- /dev/null +++ b/docs/quantity-factoryfunctions.rst @@ -0,0 +1,27 @@ +.. _quantity_factoryfunctions: + +.. toctree:: + :maxdepth: 2 + +Quantity Factory Functions +========================== + +.. code-block:: cpp + + #include + +.. doxygenfunction:: xo::unit::qty::milligrams +.. doxygenfunction:: xo::unit::qty::grams +.. doxygenfunction:: xo::unit::qty::kilograms + +.. doxygenfunction:: xo::unit::qty::millimeters +.. doxygenfunction:: xo::unit::qty::meters +.. doxygenfunction:: xo::unit::qty::kilometers + +.. doxygenfunction:: xo::unit::qty::nanoseconds +.. doxygenfunction:: xo::unit::qty::microseconds +.. doxygenfunction:: xo::unit::qty::milliseconds +.. doxygenfunction:: xo::unit::qty::seconds +.. doxygenfunction:: xo::unit::qty::minutes +.. doxygenfunction:: xo::unit::qty::hours +.. doxygenfunction:: xo::unit::qty::days diff --git a/docs/quantity-reference.rst b/docs/quantity-reference.rst new file mode 100644 index 00000000..1f3b4496 --- /dev/null +++ b/docs/quantity-reference.rst @@ -0,0 +1,12 @@ +.. _quantity-reference: + +Quantity Reference +================== + +.. toctree:: + :maxdepth: 2 + :caption: Quantity Reference + + quantity-class + quantity-factoryfunctions + quantity-unitvars diff --git a/docs/quantity-unitvars.rst b/docs/quantity-unitvars.rst new file mode 100644 index 00000000..77c09946 --- /dev/null +++ b/docs/quantity-unitvars.rst @@ -0,0 +1,45 @@ +.. _quantity-unitvars: + +.. toctree:: + :maxdepth: 2 + +Quantity Unit Variables +======================= + +.. code-block:: cpp + + #include + +The ``xo::unit::unit_qty`` namespace contains unit quantities in each dimension. +Can use these to request unit conversion involving multiple dimensions, for example: + +.. code-block:: cpp + + namespace qty = xo::unit::qty; + namespace u = xo::unit::unit_qty; + + auto q1 = (qty::kilometers(150.0) / qty::hours(0.5); + auto q2 = q1.with_units_from(u:meter / u:second); + + +Mass +---- +.. doxygenvariable:: xo::unit::unit_qty::milligram +.. doxygenvariable:: xo::unit::unit_qty::gram +.. doxygenvariable:: xo::unit::unit_qty::kilogram + +Distance +-------- +.. doxygenvariable:: xo::unit::unit_qty::millimeter +.. doxygenvariable:: xo::unit::unit_qty::meter +.. doxygenvariable:: xo::unit::unit_qty::kilometer + +Time +---- +.. doxygenvariable:: xo::unit::unit_qty::nanosecond +.. doxygenvariable:: xo::unit::unit_qty::microsecond +.. doxygenvariable:: xo::unit::unit_qty::millisecond +.. doxygenvariable:: xo::unit::unit_qty::second +.. doxygenvariable:: xo::unit::unit_qty::minute +.. doxygenvariable:: xo::unit::unit_qty::hour +.. doxygenvariable:: xo::unit::unit_qty::day diff --git a/docs/unit-concept.rst b/docs/unit-concept.rst new file mode 100644 index 00000000..387289d8 --- /dev/null +++ b/docs/unit-concept.rst @@ -0,0 +1,12 @@ +.. _unit-concept: + +Unit Concepts +============= + +.. code-block:: cpp + + #include + +.. doxygenconcept:: xo::unit::unit_concept + +.. doxygenconcept:: xo::unit::basis_unit_concept diff --git a/docs/unit-quantities.rst b/docs/unit-quantities.rst new file mode 100644 index 00000000..30e57de6 --- /dev/null +++ b/docs/unit-quantities.rst @@ -0,0 +1,93 @@ +.. _unit-quantities: + +.. toctree + :maxdepth: 2 + +Unit Quantities +=============== + +Xo-unit uses the type system to represent units. +This is great for eliminating runtime overhead. + +One place where we face some awkwardness is conversions involving multiple dimensions. +We'd like to write something concise like + +.. code-block:: cpp + + meter / (second * second); + +The difficulty arises because xo-unit represents `meter` and `second` by types +(``xo::unit::units::meter`` and ``xo::unit::units::second``); operators like `*` and `/` +apply to *values*, not types. + +We'll present various ways to express rescaling below + +Converting units +---------------- + +First, xo-unit provides constexpr unit quantities in namespace ``xo::unit::unit_qty``: + +.. code-block:: cpp + :linenos: + + static constexpr auto meter = qty::meters(1); + static constexpr auto kilometer = qty::kilometers(1); + // etc + +Second, a method ``quantity::with_units_from`` that takes units (only) from its argument: +``quantity::with_units_from`` just extracts its argument's unit_type to call ``quantity::with_units``. + +.. code-block:: cpp + :linenos: + + template + template + auto quantity::with_units_from(Quantity q) { + return this->with_units(); + } + +Motivation is that it's easier to express an argument to `with_units_from` +than to express template arguments to `with_units`. + +Prefer + +.. code-block:: cpp + :linenos: + :emphasize-lines: 5 + + namespace u = xo::unit::unit_qty; // u::meter is a value + namespace qty = xo::unit::qty; + + auto q1 = qty::kilometers(150.0) / qty::hours(0.5); + auto q2 = q1.with_units_from(u::meter / u::second); + +instead of the more verbose: + +.. code-block:: cpp + :linenos: + :emphasize-lines: 5-6 + + namespace u = xo::unit::units; // u::meter is a type + + auto q1 = qty::kilometers(150.0) / qty::hours(0.5); + + auto q2 = q1.with_units>>(); + +Using basis units +----------------- + +An alternative way to request multidimensional unit conversion is with basis units + +.. code-block:: cpp + :linenos: + :emphasize-lines: 4-5 + + namespace u = xo::unit::units; // u::meter is a type + + auto q1 = qty::kilometers(150.0) / qty::hours(0.5); + auto q2 = q1.with_basis_unit(); // q2 in km.s^-1 + auto q3 = q2.with_basis_unit(); // q3 in m.s^-1 + +With this technique we don't have to supply the basis dimension's exponent. +Instead we're just giving scale. diff --git a/docs/unit-reference.rst b/docs/unit-reference.rst new file mode 100644 index 00000000..a329f486 --- /dev/null +++ b/docs/unit-reference.rst @@ -0,0 +1,10 @@ +.. _unit-reference: + +Unit Reference +============== + +.. toctree:: + :maxdepth: 2 + :caption: Unit Reference + + unit-concept From eea10d8fe1f6d6b4fd99cd6ef9a99b79faf50a50 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Apr 2024 02:00:05 -0400 Subject: [PATCH 0600/2524] xo-unit: refactor: move promoter aux class to detail/ subdir --- include/xo/unit/detail/promoter.hpp | 40 +++++++++++++++++++++++++++++ include/xo/unit/quantity.hpp | 29 +++++---------------- 2 files changed, 47 insertions(+), 22 deletions(-) create mode 100644 include/xo/unit/detail/promoter.hpp diff --git a/include/xo/unit/detail/promoter.hpp b/include/xo/unit/detail/promoter.hpp new file mode 100644 index 00000000..1dc34c78 --- /dev/null +++ b/include/xo/unit/detail/promoter.hpp @@ -0,0 +1,40 @@ +/** @file promoter.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "unit.hpp" + +namespace xo { + namespace unit { + /** @class promoter + * + * Auxiliary class driver for quantity::promote(). + * promoter has two specializations: + * 1. if Unit is dimensionless, @c promoter::promote() is the identity function. + * This has the effect of collapsing dimensionless quantities to their representation. + * 2. if Unit has at least one non-empty dimension, + * @c promoter::promote() builds an xo::unit::quantity instance + **/ + template > + struct promoter; + + template + class quantity; + + /* collapse dimensionless quantity to its repr_type> */ + template + struct promoter { + static constexpr Repr promote(Repr x) { return x; }; + }; + + template + struct promoter { + static constexpr quantity promote(Repr x) { return quantity(x); } + }; + } /*namespace unit*/ +} /*namespace xo*/ + +/** end promoter.hpp **/ diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index f8d5dd80..138565d1 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -4,18 +4,12 @@ #include "quantity_concept.hpp" #include "unit.hpp" +#include "detail/promoter.hpp" //#include "xo/reflect/Reflect.hpp" //#include "xo/indentlog/scope.hpp" namespace xo { namespace unit { - /** @class promoter - * - * Aux class assister for quantity::promote() - **/ - template > - struct promoter; - // ----- quantity ----- /** @class quantity @@ -72,10 +66,7 @@ namespace xo { static constexpr quantity unit_quantity() { return quantity(1); } /** @brief promote representation to quantity. Same as multiplying by Unit **/ - static constexpr auto promote(Repr x) { - //std::cerr << "quantity::promote: x=" << x << ", R=" << reflect::Reflect::require()->canonical_name() << std::endl; - return promoter::promote(x); - } + static constexpr auto promote(Repr x); ///@} /** @addtogroup quantity-traits **/ @@ -500,18 +491,12 @@ namespace xo { Repr scale_ = 0; }; /*quantity*/ - // ----- promoter ----- - - /* collapse dimensionless quantity to its repr_type> */ template - struct promoter { - static constexpr Repr promote(Repr x) { return x; }; - }; - - template - struct promoter { - static constexpr quantity promote(Repr x) { return quantity(x); } - }; + constexpr auto + quantity::promote(Repr x) { + //std::cerr << "quantity::promote: x=" << x << ", R=" << reflect::Reflect::require()->canonical_name() << std::endl; + return promoter::promote(x); + } // ----- operator+ ----- From 75799f46529124848862e5e42335ac422fa57dfe Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 12 Apr 2024 20:45:04 -0400 Subject: [PATCH 0601/2524] initial commit --- CMakeLists.txt | 57 +++++++++++ cmake/xo-bootstrap-macros.cmake | 26 +++++ cmake/xo_stringliteralConfig.cmake.in | 17 ++++ example/CMakeLists.txt | 1 + example/ex1/CMakeLists.txt | 15 +++ example/ex1/ex1.cpp | 38 ++++++++ .../xo/stringliteral/string_view_concat.hpp | 30 ++++++ include/xo/stringliteral/stringliteral.hpp | 96 +++++++++++++++++++ .../stringliteral/stringliteral_iostream.hpp | 36 +++++++ 9 files changed, 316 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_stringliteralConfig.cmake.in create mode 100644 example/CMakeLists.txt create mode 100644 example/ex1/CMakeLists.txt create mode 100644 example/ex1/ex1.cpp create mode 100644 include/xo/stringliteral/string_view_concat.hpp create mode 100644 include/xo/stringliteral/stringliteral.hpp create mode 100644 include/xo/stringliteral/stringliteral_iostream.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..b7b5d586 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,57 @@ +# xo-stringliteral/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_stringliteral VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(cmake/xo-bootstrap-macros.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 + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(example) +#add_subdirectory(utest) +#add_subdirectory(docs) + +# ---------------------------------------------------------------- +# provide find_package() support for projects using this library + +set(SELF_LIB xo_stringliteral) +xo_add_headeronly_library(${SELF_LIB}) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# dependencies + +#xo_headeronly_dependency(${SELF_LIB} randomgen) +# etc.. + +# end CMakeLists.txt diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..0b6a916a --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,26 @@ +# ---------------------------------------------------------------- +# for example use +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will set +# CMAKE_MODULE_PATH = /usr/local/share/cmake +# and expect .cmake macros in +# /usr/local/share/cmake/xo_macros/xo-project-macros.cmake +# ---------------------------------------------------------------- + +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +if (NOT XO_SUBMODULE_BUILD) + message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}") + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/cmake/xo_stringliteralConfig.cmake.in b/cmake/xo_stringliteralConfig.cmake.in new file mode 100644 index 00000000..e5ee1778 --- /dev/null +++ b/cmake/xo_stringliteralConfig.cmake.in @@ -0,0 +1,17 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +#find_dependency(reflect) +#find_dependency(subsys) +#find_dependency(Eigen3) +#find_dependency(webutil) +#find_dependency(printjson) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 00000000..4151ec21 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(ex1) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..abe34974 --- /dev/null +++ b/example/ex1/CMakeLists.txt @@ -0,0 +1,15 @@ +# xo-stringliteral/example/ex1/CMakeLists.txt + +set(SELF_EXE xo_stringliteral_ex1) +set(SELF_SRCS ex1.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_stringliteral) +#xo_dependency(${SELF_EXE} reflect) + +# end CMakeLists.txt diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp new file mode 100644 index 00000000..dcb2a459 --- /dev/null +++ b/example/ex1/ex1.cpp @@ -0,0 +1,38 @@ +/* @file ex1.cpp */ + +#include "xo/stringliteral/stringliteral.hpp" +#include "xo/stringliteral/stringliteral_iostream.hpp" +#include "xo/stringliteral/string_view_concat.hpp" +#include + +int +main() { + using namespace std; + using xo::stringliteral; + using xo::stringliteral_compare; + +#ifdef NOT_USING + constexpr stringliteral s1("hello"); + + static_assert(stringliteral_compare(s1, s1) == 0); + + cerr << s1 << endl; + + constexpr stringliteral s2 = stringliteral_concat(stringliteral("hello"), + stringliteral(", world")); + +#endif + + static constexpr string_view hello("hello"); + static constexpr string_view world(" world"); + + static constexpr auto s2 = concat_v; + + static constexpr string_view hello_world("hello world"); + + static_assert(s2 == hello_world); + + cerr << hello_world << endl; +} + +/* end ex1.cpp */ diff --git a/include/xo/stringliteral/string_view_concat.hpp b/include/xo/stringliteral/string_view_concat.hpp new file mode 100644 index 00000000..4105420d --- /dev/null +++ b/include/xo/stringliteral/string_view_concat.hpp @@ -0,0 +1,30 @@ +#include +#include + +template +struct sv_concat +{ + static constexpr auto impl() noexcept { + constexpr std::size_t n = (Strings.size() + ... + 0); + + std::array arr{}; + + auto append = [i=0, &arr](const auto & s) mutable { + for (auto c : s) + arr[i++] = c; + }; + (append(Strings), ...); + arr[n] = '\0'; + + return arr; + } + + static constexpr auto arr = impl(); + static constexpr std::string_view value { + arr.data(), + arr.size() - 1 + }; +}; + +template +static constexpr auto concat_v = sv_concat::value; diff --git a/include/xo/stringliteral/stringliteral.hpp b/include/xo/stringliteral/stringliteral.hpp new file mode 100644 index 00000000..72235727 --- /dev/null +++ b/include/xo/stringliteral/stringliteral.hpp @@ -0,0 +1,96 @@ +/** @file stringliteral.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include +#include +#include + +namespace xo { + /** @class stringliteral + * + * @brief class to represent a literal string at compile time, for use as template argument + **/ + template + struct stringliteral { + constexpr stringliteral() { if (N > 0) value_[0] = '\0'; } + constexpr stringliteral(const char (&str)[N]) { std::copy_n(str, N, value_); } + constexpr int size() const { return N; } + + constexpr char const * c_str() const { return value_; } + + char value_[N]; + }; + + /** @brief all_same_v is true iff types T1 = .. = Tn + **/ + template < typename First, typename... Rest > + constexpr auto + all_same_v = std::conjunction_v< std::is_same... >; + + /** @brief concatenate string literals + * + * NOTE: this isn't constexpr in clang16 + **/ + template < typename... Ts> + constexpr auto + stringliteral_concat(Ts && ... args) + { +#ifdef NOT_USING + static_assert(all_same_v...>, + "string must share the same char type"); + + using char_type = std::remove_const_t< std::remove_pointer_t < std::common_type_t < Ts... > > >; +#endif + using char_type = char; + + /** n1: total number of bytes used by arguments **/ + constexpr size_t n1 = (sizeof(Ts) + ...); + /** z1: each string arg has a null terminator included in its size, + * z1 is the number of arguments in parameter pack Ts, + * which equals the number of null terminators used + **/ + constexpr size_t z1 = sizeof...(Ts); + + /** n: number of chars in concatenated string. +1 for final null **/ + constexpr size_t n + = (n1 / sizeof(char_type)) - z1 + 1; + + stringliteral result; + size_t pos = 0; + + auto detail_concat = [ &pos, &result ](auto && arg) { + constexpr auto count = (sizeof(arg) - sizeof(char_type)) / sizeof(char_type); + + std::copy_n(arg.c_str(), count, result.value_ + pos); + pos += count; + }; + + (detail_concat(args), ...); + + //return stringliteral(""); + return result; + } + +#ifdef NOT_USING + template + constexpr auto + stringliteral_compare(stringliteral && s1, stringliteral && s2) + { + return std::string_view(s1.value_) <=> std::string_view(s2.value_); + } +#endif + + template + constexpr auto + stringliteral_compare(const stringliteral & s1, const stringliteral & s2) + { + return std::string_view(s1.value_) <=> std::string_view(s2.value_); + } +} /*namespace xo*/ + + +/** end stringliteral.hpp **/ diff --git a/include/xo/stringliteral/stringliteral_iostream.hpp b/include/xo/stringliteral/stringliteral_iostream.hpp new file mode 100644 index 00000000..18a371a5 --- /dev/null +++ b/include/xo/stringliteral/stringliteral_iostream.hpp @@ -0,0 +1,36 @@ +/** @file stringliteral_iostream.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "stringliteral.hpp" +#include +//#include + +namespace xo { + /** @brief print stringliteral on stream os. + * + **/ + template + void + print_stringliteral (std::ostream & os, const stringliteral & x) { + os << x.c_str(); + } + + /** @brief print stringliteral x on stream os. + * + * Example + * @code + * cout << stringliteral("foo"); // outputs "foo" + **/ + template + inline std::ostream & + operator<< (std::ostream & os, const stringliteral & x) { + print_stringliteral(os, x); + return os; + } +} /*namespace xo*/ + +/** end stringliteral_iostream.hpp **/ From c8ac0ceb493c03af3edbcb008503b001bcba50be Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 13:30:16 -0400 Subject: [PATCH 0602/2524] cmake: + xo_self_headeronly_dependency() --- cmake/xo_macros/xo_cxx.cmake | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index e63762f8..3fcf3e2f 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -736,6 +736,19 @@ macro(xo_self_dependency target dep) target_link_libraries(${target} PUBLIC ${dep}) endmacro() +# dependency on target provided from this codebase. +# +# Similar comments as for xo_self_dependency() +# 1. don't need find_package() in this case, since details of dep targets +# must be known to cmake for it to build them. +# 2. in any case, can't use find_package() when cmake runs, +# because supporting .cmake files haven't been generated yet +# 3. need to use INTERFACE instead of PUBLIC for a header-only dep +# +macro(xo_self_headeronly_dependency target dep) + target_link_libraries(${target} INTERFACE ${dep}) +endmacro() + # ---------------------------------------------------------------- # need this when linking pybind11-generated libraries # From a958217c38470c5e49a67770c3d9abb8c0338fc6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 17:25:14 -0400 Subject: [PATCH 0603/2524] xo-flatstring: + README + docs + test coverage --- CMakeLists.txt | 46 +- README.md | 59 + cmake/gen-ccov.in | 20 + cmake/lcov-harness | 114 + cmake/xo-bootstrap-macros.cmake | 5 +- ....cmake.in => xo_flatstringConfig.cmake.in} | 0 docs/CMakeLists.txt | 110 + docs/Doxyfile.in | 2816 +++++++++++++++++ docs/_static/README | 1 + docs/conf.py | 35 + docs/flatstring-class.rst | 60 + docs/flatstring-functions.rst | 15 + docs/flatstring-reference.rst | 11 + docs/index.rst | 38 + docs/install.rst | 56 + docs/lessons.rst | 44 + example/ex1/ex1.cpp | 149 +- include/xo/flatstring/flatstring.hpp | 414 +++ include/xo/flatstring/flatstring_iostream.hpp | 37 + .../string_view_concat.hpp | 0 include/xo/stringliteral/stringliteral.hpp | 96 - .../stringliteral/stringliteral_iostream.hpp | 36 - utest/.#flatstring_utest_main.cpp | 1 + utest/CMakeLists.txt | 21 + utest/flatstring.test.cpp | 131 + utest/flatstring_utest_main.cpp | 6 + 26 files changed, 4163 insertions(+), 158 deletions(-) create mode 100644 README.md create mode 100644 cmake/gen-ccov.in create mode 100755 cmake/lcov-harness rename cmake/{xo_stringliteralConfig.cmake.in => xo_flatstringConfig.cmake.in} (100%) create mode 100644 docs/CMakeLists.txt create mode 100644 docs/Doxyfile.in create mode 100644 docs/_static/README create mode 100644 docs/conf.py create mode 100644 docs/flatstring-class.rst create mode 100644 docs/flatstring-functions.rst create mode 100644 docs/flatstring-reference.rst create mode 100644 docs/index.rst create mode 100644 docs/install.rst create mode 100644 docs/lessons.rst create mode 100644 include/xo/flatstring/flatstring.hpp create mode 100644 include/xo/flatstring/flatstring_iostream.hpp rename include/xo/{stringliteral => flatstring}/string_view_concat.hpp (100%) delete mode 100644 include/xo/stringliteral/stringliteral.hpp delete mode 100644 include/xo/stringliteral/stringliteral_iostream.hpp create mode 120000 utest/.#flatstring_utest_main.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/flatstring.test.cpp create mode 100644 utest/flatstring_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b7b5d586..79d62595 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ # xo-stringliteral/CMakeLists.txt -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.25) -project(xo_stringliteral VERSION 1.0) +project(xo_flatstring VERSION 1.0) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) @@ -12,17 +12,37 @@ include(cmake/xo-bootstrap-macros.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. +# ---------------------------------------------------------------- +# cmake -DCMAKE_BUILD_TYPE=coverage + +if (NOT DEFINED PROJECT_CXX_FLAGS_COVERAGE) + # note: for clang would use -fprofile-instr-generate -fcoverage-mapping here instead and also at link time + set(PROJECT_CXX_FLAGS_COVERAGE ${PROJECT_CXX_FLAGS} -ggdb -Og -fprofile-arcs -ftest-coverage + CACHE STRING "coverage c++ compiler flags") +endif() +message("-- PROJECT_CXX_FLAGS_COVERAGE: coverage c++ flags are [${PROJECT_CXX_FLAGS_COVERAGE}]") + +add_compile_options("$<$:${PROJECT_CXX_FLAGS_COVERAGE}>") +# when -DCMAKE_BUILD_TYPE=coverage, link executables with gcov +link_libraries("$<$:gcov>") + +find_program(LCOV_EXECUTABLE NAMES lcov) +find_program(GENHTML_EXECUTABLE NAMES genhtml) + +# with coverage build: +# 1. invoke instrumented executables for which you want coverage: +# (cd path/to/build && ctest) +# 2. post-process low-level coverage data +# (path/to/build/gen-ccov) +# 3. point browser to generated html data +# file:///path/to/build/ccov/html/index.html # -# 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/*) +configure_file( + ${PROJECT_SOURCE_DIR}/cmake/gen-ccov.in + ${PROJECT_BINARY_DIR}/gen-ccov) + +file(CHMOD ${PROJECT_BINARY_DIR}/gen-ccov PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) # ---------------------------------------------------------------- # c++ settings @@ -37,8 +57,8 @@ xo_toplevel_compile_options() # ---------------------------------------------------------------- add_subdirectory(example) -#add_subdirectory(utest) -#add_subdirectory(docs) +add_subdirectory(utest) +add_subdirectory(docs) # ---------------------------------------------------------------- # provide find_package() support for projects using this library diff --git a/README.md b/README.md new file mode 100644 index 00000000..856b61fb --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# flatstring library + +Fixed-length no-allocation string implementation. + +Features: +- char array representation with maximum size set at compile time. +- compile time construction from char array and string concatenation +- pointer-free implementation, instances can be used as template arguments +- To the extent practical, provides the same api as `std::string` + +Limitations: +- requires c++20 +- not resizable. +- does not support wide characters. + +## Getting started + +### build + install +``` +$ cd xo-flatstring +$ mkdir .build +$ PREFIX=/usr/local # for example +$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -B .build +$ cmake --build .build +$ cmake --install .build +``` + +### build documentation +``` +$ cd xo-flatstring +$ cmake --build .build -- docs +``` +When complete, point local browser to `xo-flatstring/.build/docs/sphinx/index.html` + +### build with test coverage +``` +$ cd xo-flatstring +$ mkdir .build-ccov +$ cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug -B .build-ccov +$ cmake --build .build-ccov +``` + +run coverage-enabled unit tests +``` +$ (cd .build-ccov && ctest) +``` + +generate html+text coverage report +``` +$ .build-ccov/gen-ccov +``` + +browse to `.build-ccov/ccov/html/index.html` + +### LSP support +``` +$ cd xo-flatstring +$ ln -s .build/compile_commands.json +``` diff --git a/cmake/gen-ccov.in b/cmake/gen-ccov.in new file mode 100644 index 00000000..e335aed4 --- /dev/null +++ b/cmake/gen-ccov.in @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +srcdir=@PROJECT_SOURCE_DIR@ +builddir=@PROJECT_BINARY_DIR@ +lcov=@LCOV_EXECUTABLE@ +genhtml=@GENHTML_EXECUTABLE@ + +if [[ $lcov == "LCOV_EXECUTABLE-NOTFOUND" ]]; then + echo "gen-ccov: lcov executable not found" + exit 1 +fi + +if [[ $genhtml == "GENHTML_EXECUTABLE-NOTFOUND" ]]; then + echo "gen-ccov: genhtml executable not found" + exit 1 +fi + +mkdir $builddir/ccov + +$srcdir/cmake/lcov-harness $srcdir $builddir $builddir/ccov/out $lcov $genhtml diff --git a/cmake/lcov-harness b/cmake/lcov-harness new file mode 100755 index 00000000..27ac8be9 --- /dev/null +++ b/cmake/lcov-harness @@ -0,0 +1,114 @@ +#!/usr/bin/env bash + +srcdir=$1 +builddir=$2 +outputstem=$3 +lcov=$4 +genhtml=$5 + +if [[ -z "${srcdir}" ]]; then + echo "lcov-harness: expected non-empty srcdir" + exit 1 +fi + +if [[ -z ${builddir} ]]; then + echo "lcov-harness: expected non-empty builddir" + exit 1 +fi + +if [[ -z ${outputstem} ]]; then + echo "lcov-harness: expected non-empty outputstem" + exit 1 +fi + +if [[ -z ${lcov} ]]; then + echo "lcov-harness: exepcted non-empty lcov" + exit 1 +fi + +if [[ -z ${genhtml} ]]; then + echo "lcov-harness: expected non-empty genhtml" + exit 1 +fi + +# directory stems for location of {.gcda, gcno} coverage information, +# +# if we have source tree: +# +# ${srcdir} +# +- foo +# | \- foo.cpp +# \- bar +# \- quux +# +- quux.cpp +# \- quux_main.cpp +# +# then we expect build tree: +# +# ${builddir} +# +- foo +# | \- CMakeFiles +# | \- foo_target.dir +# | +- foo.cpp.gcda +# | \- foo.cpp.gcno +# +- bar +# \- quux +# \- CMakeFiles +# \- target4quux.dir +# +- quux.cpp.gcda +# +- quux.cpp.gcno +# +- quux_main.cpp.gcda +# \- quux_main.cpp.gcno +# +# in which case will have cmd_body: +# +# ${primarydirs} +# ./foo/CMakeFiles/foo_target.dir +# ./bar/quux/CMakeFiles/target4quux.dir +# +# here foo_target, quux_target are whatever build is using for corresponding cmake target names. +# +# We want to invoke lcov like: +# +# lcov --capture \ +# --output ${builddir}/ccov \ +# --exclude /utest/ \ +# --base-directory ${srcdir}/foo --directory ${builddir}/foo/CMakeFiles/foo_target.dir \ +# --base-directory ${srcdir}/bar/quux --directory ${builddir}/bar/quux/CMakeFiles/target4quux.dir +# +primarydirs=$(cd ${builddir} && find -name '*.gcno' \ + | xargs --replace=xx dirname xx \ + | uniq \ + | sed -e 's:^\./::') + +#echo "primarydirs=${primarydirs}" + +cmd="${lcov} --output ${outputstem}.info --capture --ignore-errors source" + +for bdir in ${primarydirs}; do + sdir=$(dirname $(dirname ${bdir})) + + cmd="${cmd} --base-directory ${srcdir}/${sdir} --directory ${builddir}/${bdir}" +done + +#echo cmd=${cmd} + +set -x + +# capture +${cmd} + +# keep only files with paths under source tree +# (don't want coverage for external libraries such as libstdc++ etc) +${lcov} --extract ${outputstem}.info "${srcdir}/*" --output ${outputstem}2.info + +# remove unit test dirs +# (we're interested in coverage of our installed code, not of the unit tests that exercise it) +${lcov} --remove ${outputstem}2.info '*/utest/*' --output ${outputstem}3.info + +# generate .html tree +mkdir -p ${builddir}/ccov/html +${genhtml} --ignore-errors source --show-details --prefix ${srcdir} --output-directory ${builddir}/ccov/html ${outputstem}3.info + +# also send report to stdout +${lcov} --list ${outputstem}3.info diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 0b6a916a..83f4a39b 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -15,7 +15,7 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL pre endif() if (NOT XO_SUBMODULE_BUILD) - message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}") + message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}") message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") endif() @@ -23,4 +23,5 @@ endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +#include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) # not using v1 code-coverage; testing cmake-examples impl instead diff --git a/cmake/xo_stringliteralConfig.cmake.in b/cmake/xo_flatstringConfig.cmake.in similarity index 100% rename from cmake/xo_stringliteralConfig.cmake.in rename to cmake/xo_flatstringConfig.cmake.in diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 00000000..0751be74 --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,110 @@ +# xo-stringliteral/docs/CMakeLists.txt + +if (XO_SUBMODULE_BUILD) + # in submodule build, rely on toplevel docs/CMakeLists.txt file instead +else() + # build docs starting from here only in standalone build. + # otherwise use top-level doxygen setup instead. + + set(ALL_LIBRARY_TARGETS xo_stringliteral) # todo: automate this from xo-cmake macros + set(ALL_UTEST_TARGETS xo_stringliteral_ex1 ) # todo: automate this from xo-cmake macros + + # look for doxygen executable + find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED) + message("-- DOXYGEN_EXECUTABLE=${DOXYGEN_EXECUTABLE}") + + # look for sphinx-build executable + find_program(SPHINX_EXECUTABLE NAMES sphinx-build REQUIRED) + message("-- SPHINX_EXECUTABLE=${SPHINX_EXECUTABLE}") + + set(DOX_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + + set(DOX_INPUT_DIR ${PROJECT_SOURCE_DIR}) + set(DOX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dox) + + set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html) + + # .hpp files reachable from xo-stringliteral/include + # + # REMINDER: for reliability will need to re-run cmake when the set of .hpp files changes + # + file(GLOB_RECURSE DOX_HPP_FILES_GLOB ${PROJECT_SOURCE_DIR}/include *.hpp) + + set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/sphinx/html) + set(SPHINX_INDEX_FILE ${SPHINX_OUTPUT_DIR}/index.html) + # + # sphinx .rst files reachable from cmake-examples/docs + # + # REMINDER: for reliability will need to re-run cmake when the set of .rst files changes + # + file(GLOB_RECURSE SPHINX_RST_FILES_GLOB ${CMAKE_CURRENT_SOURCE_DIR} *.rst) + + set(SPHINX_RST_FILES index.rst install.rst lessons.rst flatstring-reference.rst flatstring-class.rst) + + # TODO: + # 1. move Doxyfile.in to xo-cmake project + # 2. replace this command section with xo-cmake macro + # + configure_file( + Doxyfile.in ${DOX_CONFIG_FILE} + FILE_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE + @ONLY) + + set(DOX_DEPS ${ALL_LIBRARY_TARGETS} ${ALL_UTEST_TARGETS} ${DOX_HPP_FILES_GLOB}) + + file(MAKE_DIRECTORY ${DOX_OUTPUT_DIR}) + add_custom_command( + OUTPUT ${DOX_INDEX_FILE} + DEPENDS ${DOX_DEPS} + COMMAND "${DOXYGEN_EXECUTABLE}" ${DOX_CONFIG_FILE} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + MAIN_DEPENDENCY ${DOX_CONFIG_FILE} + COMMENT "Generating docs (doxygen)") + + # To build this target + # $ cmake --build .build -j -- doxygen + # or + # $ cd .build + # $ make doxygen + # + add_custom_target( + doxygen + DEPENDS ${DOX_INDEX_FILE} ${DOX_DEPS} + ) + + # root of sphinx doc tree + set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) + set(SPHINX_DEPS doxygen conf.py ${SPHINX_RST_FILES} ${SPHINX_RST_FILES_GLOB} ${DOX_DEPS}) + + add_custom_command( + OUTPUT ${SPHINX_INDEX_FILE} + DEPENDS ${SPHINX_DEPS} + COMMAND ${SPHINX_EXECUTABLE} + -b html -Dbreathe_projects.xodoxxml=${CMAKE_CURRENT_BINARY_DIR}/dox/xml + ${SPHINX_SOURCE} ${SPHINX_OUTPUT_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating docs (sphinx) -> [${SPHINX_OUTPUT_DIR}]") + + # make sphinx --> generate sphinx documentation + # + add_custom_target( + sphinx + DEPENDS ${SPHINX_INDEX_FILE}) + + # - html docs generated in build/docs/sphinx + # - copy the doc tree to share/doc/xo_unit/html + # + # OPTIONAL: install directory tree if it exists, + # but don't complain if it's missing + install( + DIRECTORY ${SPHINX_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME} + COMPONENT Documentation + OPTIONAL) + + # make docs --> generate sphinx documentation + add_custom_target( + docs + DEPENDS sphinx) +endif() diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in new file mode 100644 index 00000000..13dfb0fa --- /dev/null +++ b/docs/Doxyfile.in @@ -0,0 +1,2816 @@ +# If filename is Doxyfile.in: +# template (to be expanded by cmake) for real doxyfile +# @SOMEVAR@ expands to value of cmake variable SOMEVAR +# +# expressions to be expanded include: +# @DOX_INPUT_DIR@ +# @DOX_OUTPUT_DIR@ +# +# if filename is Doxyfile: +# expanded template in build directory, to configure doxygen + +# Doxyfile 1.9.7 +# + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Cmake Examples" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @DOX_OUTPUT_DIR@ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = @DOX_INPUT_DIR@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN Use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0. and GITHUB Use the lower case version of title +# with any whitespace replaced by '-' and punctations characters removed.. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = YES + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = YES + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = YES + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = SYSTEM + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @DOX_INPUT_DIR@ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = */utest/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /Node, +# Edge and Graph Attributes specification You need to make sure dot is able +# to find the font, which can be done by putting it in a standard location or by +# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. Default graphviz fontsize is 14. +# The default value is: fontname=Helvetica,fontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_COMMON_ATTR = "fontname=lato,fontsize=10" +#DOT_COMMON_ATTR = "fontname=HelveticaNeue,fontsize=10" + +# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can +# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about +# arrows shapes. +# The default value is: labelfontname=Helvetica,labelfontsize=10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_EDGE_ATTR = "labelfontname=lato,labelfontsize=10" +#DOT_EDGE_ATTR = "labelfontname=HelveticaNeue,labelfontsize=10" + +# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes +# around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification +# The default value is: shape=box,height=0.2,width=0.4. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" + +# You can set the path where dot can find font specified with fontname in +# DOT_COMMON_ATTR and others dot attributes. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will +# generate a graph for each documented class showing the direct and indirect +# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and +# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case +# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the +# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used. +# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance +# relations will be shown as texts / links. +# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN. +# The default value is: YES. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. See also the chapter Grouping +# in the manual. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag UML_LOOK is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +TEMPLATE_RELATIONS = NO + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDE_GRAPH = YES + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = YES + +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# https://www.graphviz.org/)). +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = NO + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate +# files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc temporary +# files. +# The default value is: YES. + +DOT_CLEANUP = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will +# use a built-in version of mscgen tool to produce the charts. Alternatively, +# the MSCGEN_TOOL tag can also specify the name an external tool. For instance, +# specifying prog as the value, doxygen will call the tool as prog -T +# -o . The external tool should support +# output file formats "png", "eps", "svg", and "ismap". + +MSCGEN_TOOL = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = diff --git a/docs/_static/README b/docs/_static/README new file mode 100644 index 00000000..8230095c --- /dev/null +++ b/docs/_static/README @@ -0,0 +1 @@ +add any static {.html, .js, ..} files for sphinx to pickup here \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..493bc187 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,35 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'xo stringlit documentation' +copyright = '2024, Roland Conybeare' +author = 'Roland Conybeare' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +#extensions = [] +extensions = [ "breathe", + "sphinx.ext.autodoc" # generate info from docstrings + ] + +# note: breathe requires doxygen xml output -> must have GENERATE_XML = YES in Doxyfile.in +# match project name in Doxyfile.in +breathe_default_project = "xodoxxml" + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +pygments_style = 'sphinx' + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +#html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] diff --git a/docs/flatstring-class.rst b/docs/flatstring-class.rst new file mode 100644 index 00000000..d2c9c546 --- /dev/null +++ b/docs/flatstring-class.rst @@ -0,0 +1,60 @@ +.. _flatstring-class: + +Flatstring +========== + +.. code-block:: cpp + + #include + +A ``flatstring`` is a thin wrapper around a character array. + +.. doxygenclass:: xo::flatstring + +Instance Variables +------------------ + +.. doxygengroup:: flatstring-instance-variables + :content-only: + +Constants +--------- + +.. doxygengroup:: flatstring-constants + :content-only: + +Constructors +------------ + +.. doxygengroup:: flatstring-ctor + :content-only: + +Properties +---------- + +.. doxygengroup:: flatstring-properties + :content-only: + +Access Methods +-------------- + +.. doxygengroup:: flatstring-access + :content-only: + +Iterators +--------- + +.. doxygengroup:: flatstring-iterators + :content-only: + +Assignment +---------- + +.. doxygengroup:: flatstring-assign + :content-only: + +Conversion +---------- + +.. doxygengroup:: flatstring-conversion-operators + :content-only: diff --git a/docs/flatstring-functions.rst b/docs/flatstring-functions.rst new file mode 100644 index 00000000..7e3c1fb1 --- /dev/null +++ b/docs/flatstring-functions.rst @@ -0,0 +1,15 @@ +.. _flatstring_functions: + +.. toctree:: + :maxdepth: 2 + +Flatstring Functions +==================== + +.. code-block:: cpp + + #include + +.. doxygenfunction:: xo::flatstring_concat + +.. doxygenfunction:: xo::flatstring_compare diff --git a/docs/flatstring-reference.rst b/docs/flatstring-reference.rst new file mode 100644 index 00000000..baf8f75e --- /dev/null +++ b/docs/flatstring-reference.rst @@ -0,0 +1,11 @@ +.. _flatstring-reference: + +Flatstring Reference +==================== + +.. toctree:: + :maxdepth: 2 + :caption: Flatstring Reference + + flatstring-class + flatstring-functions diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..430233d8 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,38 @@ +xo-flatstring documentation master file + +xo-flatstring documentation +=========================== + +xo-flatstring is a lightweight header-only library that provides a constexpr +fixed-size no-allocation string implementation. + +Why ``flatstring``? + +1. ``flatstring`` instances can be used as template arguments. [1]_ + +2. ``flatstring`` operations (construction, concatenation, ...) are ``constexpr``, so can be done at compile time. [2]_ + +3. a ``flatstring`` expression can occupy both compile-time and runtime roles. [3]_ + +.. [1] A fixed-size char array *can* be used as a template + argument, but char* pointers cannot. Automatic conversion of char arrays to pointers in various contexts + makes them difficult to work with in c++ templates. + +.. [2] Although allocation is permitted in constexpr code, it's subject to several restrictions. + it's not yet possible (as of c++23) to use ``std::string`` at compile time. + +.. [3] contrast with a solution relying on template arguments, which must then be compile-time-only. + +.. toctree:: + :maxdepth: 2 + :caption: xo-flatstring contents: + + install + lessons + flatstring-reference + +Indices and Tables +------------------ + +* :ref:`genindex` +* :ref:`search` diff --git a/docs/install.rst b/docs/install.rst new file mode 100644 index 00000000..aab059ab --- /dev/null +++ b/docs/install.rst @@ -0,0 +1,56 @@ +.. _install: + +.. toctree + :maxdepth: 2 + +Install +======= + +`xo-flatstring source`_ lives on github. + +.. _xo-flatstring source: https://github.com/rconybea/xo-flatstring + +Implementation relies on c++20 features (for example class-instances as template arguments). +Tested with gcc 13.2 + +Include as submodule +-------------------- + +.. code-block:: bash + + cd myproject + git submodule add -b main https://github.com/rconybea/xo-flatstring ext/xo-flatstring + git submodule update --init + +This assumes you organize directly-incorporated dependencies under directory ``myproject/ext``. +You would then add ``myproject/ext/xo-flatstring/include`` to your compiler's include path, +and from c++ do something like + +.. code-block:: c++ + + #include + +in c++ source files that rely on xo-flatstring + +Supported compilers +------------------- + +* developed with gcc 13.2.0; github CI using gcc 11.4.0 (asof April 2024) + +Building from source +-------------------- + +Although the xo-flatstring library is header-only, unit tests have some dependencies. +Example instructions (github CI) for build starting from stock ubuntu are in `ubuntu-main.yml`_ + +.. _ubuntu-main.yml: https://github.com/Rconybea/xo-flatstring/blob/main/.github/workflows/ubuntu-main.yml + +Unit test dependencies: + +* `catch2`_ header-only unit-test framework +* `xo-cmake`_ cmake macros +* `xo-indentlog`_ logging with call-structure indenting + +.. _catch2: https://github.com/catchorg/Catch2 +.. _xo-cmake: https://github.com/rconybea/xo-cmake +.. _xo-indentlog: https://github.com/rconybea/indentlog diff --git a/docs/lessons.rst b/docs/lessons.rst new file mode 100644 index 00000000..40c39eaf --- /dev/null +++ b/docs/lessons.rst @@ -0,0 +1,44 @@ +.. _lessons: + +.. toctree + :maxdepth: 2 + +Lessons +======= + +This is a rogue's gallery of experiments, typically unsuccessful. +One hurdle we've created for ourselves, is we need both gcc and clang to agree +that an expression can be computed at compile-time; +otherwise will get false alarms in our IDE (raised by LSP running in the background, which relies on clang). + +Must Fully Initialize Memory +---------------------------- + +Struggled for a while with the implementation of :ref:xo::flatstring_concat + +.. code-block:: cpp + + template + flatstring::flatstring() { + if (N > 0) + value_[0] = '\0'; + } + + +This implementation satisfies gcc, but not clang: in the following snippet, clang doesn't recognize ``tmp`` as constexpr: + +.. code-block:: cpp + + constexpr n = ...; + flatstring tmp; + + static_assert(tmp.size() == ...); // tmp not constexpr! + +Correction is to prove to clang that every memory address owned by an empty ``flatstring`` is initialized: + +.. code-block:: cpp + + template + flatstring::flatstring() { + std::fill_n(value_, N, '\0'); + } diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index dcb2a459..e343002e 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -1,38 +1,165 @@ /* @file ex1.cpp */ -#include "xo/stringliteral/stringliteral.hpp" -#include "xo/stringliteral/stringliteral_iostream.hpp" -#include "xo/stringliteral/string_view_concat.hpp" +#include "xo/flatstring/flatstring.hpp" +//#include "xo/stringliteral/stringliteral_iostream.hpp" +#include "xo/flatstring/experiment.hpp" +//#include "xo/stringliteral/string_view_concat.hpp" #include int main() { using namespace std; - using xo::stringliteral; + using xo::flatstring; +#ifdef WAITAMO using xo::stringliteral_compare; +#endif + + static_assert(foo1().x_ == 1); + static_assert(foo1().y_ == 2); + + constexpr foo1 s1; + + static_assert(s1.x_ == 1); + static_assert(s1.y_ == 2); + + constexpr foo2 s2; + + static_assert(s2.v_[0] == 'a'); + static_assert(s2.v_[1] == 'b'); + + constexpr foo3<2> s3; + + static_assert(s3.v_[0] == 'a'); + static_assert(s3.v_[1] == 'b'); + + constexpr foo4<6> s4("hello"); + + constexpr foo5 s5("hello"); + + static_assert(s5.v_[0] == 'h'); + static_assert(s5.v_[5] == '\0'); + + constexpr foo6 s6("hello", ", world!"); + + static_assert(s6.v_[0] == 'h'); + static_assert(s6.size() == 13); + + cerr << "s6=" << s6.c_str() << endl; + + /* z gives allocation size. string size is z-1 */ + constexpr std::size_t z = concat_size("hello", ", world!", " What's up?"); + + static_assert(z == 25); + + constexpr foo7<10> s7("Hello", ", world!", " What's up?"); + + constexpr stringlit<10> s8("0123", "45678"); + + static_assert(s8.size() == 9); + constexpr std::size_t z8 = stringlit_capacity(s8); + + + static_assert(sizeof("0123") == 5); + static_assert(sizeof("45") == 3); + static_assert(sizeof("78") == 3); + + static_assert(literal_strlen("0123") == 4); + + + static_assert(z8 == 10); #ifdef NOT_USING - constexpr stringliteral s1("hello"); + static_assert(count_size("0123") == 5); + static_assert(count_size("0123", "45") == 7); + static_assert(count_size("0123", "45", "67", "8") == 10); + + constexpr auto z9 = count_size("0123", "45", "78"); + + static_assert(z9 == 9); + + constexpr auto z10 = foofn("0123"); + + static_assert(z10 == 5); +#endif + + //constexpr auto z11 = foofn2("0123"); + + //static_assert(z9 > 22); + + constexpr auto s9 = stringlit_make("0123", "456", "78"); + //constexpr auto s9 = stringlit_makepalooza("0123", "45678"); + + static_assert(s9.size() == 9); + + constexpr auto s10 = stringlit_make("0", "123", "456", "78"); + + static_assert(s10.size() == 9); + + cerr << s10.c_str() << endl; + +#ifdef NOT_SUCCESSFUL + constexpr auto s11 = stringlit_make("0", "1", "23", "456", "78"); +#endif + + constexpr std::size_t z9 = stringlit_capacity(s9, s10); + + static_assert(z9 == 19); + + constexpr auto s12 = stringlit_cat(s9, s10); + + static_assert(s12.size() == 18); + + cerr << s12.c_str() << endl; + + constexpr auto s13 = stringlit_cat(s9, s10, s12); + + static_assert(s13.size() == 36); + + cerr << s13.c_str() << endl; + +#ifdef NOT_USING static_assert(stringliteral_compare(s1, s1) == 0); cerr << s1 << endl; - - constexpr stringliteral s2 = stringliteral_concat(stringliteral("hello"), - stringliteral(", world")); - #endif + constexpr flatstring s14 = flatstring_concat(flatstring("foo"), flatstring("bar")); + + static_assert(s14.fixed_capacity == 7); + static_assert(sizeof(s14) == 7); + + constexpr flatstring s15 = flatstring_concat(flatstring("hello"), + ", ", + flatstring("world")); + static_assert(s15.fixed_capacity == 13); + static_assert(sizeof(s15) == 13); + + constexpr auto s16 = xo::flatstring_concat("foo", "bar"); + + static_assert(s16.fixed_capacity == 7); + + constexpr auto cmp = flatstring_compare(s14, s14); + + static_assert(cmp == 0); + +#ifdef WAITAMO + constexpr stringliteral s2 = stringliteral_stringlit_make(stringliteral("hello"), + stringliteral(", world")); +#endif + +#ifdef NOT_USING static constexpr string_view hello("hello"); static constexpr string_view world(" world"); - static constexpr auto s2 = concat_v; + static constexpr auto s3 = stringlit_make_v; static constexpr string_view hello_world("hello world"); - static_assert(s2 == hello_world); + static_assert(s3 == hello_world); cerr << hello_world << endl; +#endif } /* end ex1.cpp */ diff --git a/include/xo/flatstring/flatstring.hpp b/include/xo/flatstring/flatstring.hpp new file mode 100644 index 00000000..5a68bd6a --- /dev/null +++ b/include/xo/flatstring/flatstring.hpp @@ -0,0 +1,414 @@ +/** @file flatstring.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include +#include +#include +#include + +namespace xo { + /** @class flatstring + * @brief class to represent a string with a fixed amount of storage space. + * + * - Flatstring memory layout is a fixed-size, null-terminated char array. + * - With a few exceptions, flatstring methods are noexcept. + * @c flatstring::at() may throw, for consistency with @c std::string::at() behavior + * - Construction and concatenation of flatstrings are constexpr, + * and can be done at compile time. + * We rely on this in related projects (e.g. https://github.com:rconybea/xo-unit) + * - Preserves as much of the c++23 @c std::string api as practicable + * + * @c N includes mandatory null terminator, so we require @c N > 0. + * + * @invariant all flatstring instances are null-terminated. + * @invariant sizeof(flatstring) == N + **/ + template + struct flatstring { + /** @defgroup flatstring-types template types **/ + ///@{ + using traits_type = std::char_traits; + using value_type = char; + using allocator_type = std::allocator; + using size_type = std::allocator_traits::size_type; + using difference_type = std::allocator_traits::difference_type; + using reference = value_type &; + using const_reference = const value_type &; + using pointer = std::allocator_traits::pointer; + using const_pointer = std::allocator_traits::const_pointer; + using iterator = char *; + using const_iterator = const char *; + using reverse_iterator = char *; + using const_reverse_iterator = const char *; + ///@} + + /** @defgroup flatstring-constants constants **/ + ///@{ + static constexpr const size_type npos = size_type(-1); + + /** @brief capacity of this flatstring, including final null terminator. + * + * @note not present in @c std::string api + **/ + static constexpr const std::size_t fixed_capacity = N; + ///@} + + public: + /** @defgroup flatstring-ctor constructors **/ + ///@{ + /** @brief create empty string literal. Will contain N null characters + * + * Example + * @code + * constexpr flatstring<5> s1; + * static_assert(s1.empty()); + * @endcode + **/ + constexpr flatstring() noexcept { + /* note: clang verifies that we fully initialize memory; otherwise will not recognize + * instance as constexpr + */ + std::fill_n(value_, N, '\0'); + } + + /** @brief create string literal from a correctly-sized char array + * + * Example + * @code + * constexpr flatstring s1("hello"); + * static_assert(s1.size() > 0); + * @endcode + **/ + constexpr flatstring(const char (&str)[N]) noexcept { + std::copy_n(str, N, value_); + } + ///@} + + /** @defgroup flatstring-properties property-methods **/ + ///@{ + /** @brief true if (and only if) string is empty **/ + constexpr bool empty() const noexcept { return value_[0] == '\0'; } + /** @brief returns current size of this string **/ + constexpr size_type size() const noexcept { + return this->cend() - this->cbegin(); + } + /** @brief synonym for @c size() **/ + constexpr size_type length() const noexcept { return size(); } + + constexpr size_type capacity() const noexcept { return fixed_capacity - 1; } + constexpr size_type max_size() const noexcept { return fixed_capacity - 1; } + + /** @brief contents as plain old C-style string. **/ + constexpr const char * c_str() const noexcept { return value_; } + ///@} + + /** @defgroup flatstring-access access methods **/ + ///@{ + /** @brief return char at position @p pos in this string (counting from zero). + * + * Throws @c std::out_of_range exception if @p pos >= @c N + **/ + constexpr value_type & at(size_type pos) throw() { return this->at_aux(pos); } + constexpr const value_type & at(size_type pos) const throw() { return const_cast(this)->at_aux(pos); } + + /** @brief return char at position @p pos in this string (counting from zero). + * + * Does not check bounds: undefined behavior if @p pos >= @c N + * + * @pre 0<=pos<=N-1 + **/ + constexpr value_type & operator[](size_type pos) { return value_[pos]; } + constexpr const value_type & operator[](size_type pos) const { return value_[pos]; } + ///@} + + /** @defgroup flatstring-iterators iterators **/ + ///@{ + constexpr iterator begin() { return &value_[0]; } + constexpr iterator end() { return this->last(); } + constexpr const_iterator cbegin() const { return &value_[0]; } + constexpr const_iterator cend() const { return const_cast(this)->last(); } + constexpr const_iterator begin() const { return cbegin(); } + constexpr const_iterator end() const { return cend(); } + + constexpr reverse_iterator rbegin() { return this->last(); } + constexpr reverse_iterator rend() { return &value_[0]; } + constexpr const_reverse_iterator crbegin() const { return const_cast(this)->last(); } + constexpr const_reverse_iterator crend() const { return &value_[0]; } + constexpr const_reverse_iterator rbegin() const { return crbegin(); } + constexpr const_reverse_iterator rend() const { return crend(); } + ///@} + + /** @defgroup flatstring-assign assignment **/ + ///@{ + /** @brief put string into empty state. fills entire char array with nulls **/ + void clear() { std::fill_n(value_, N, '\0'); } + + /** @brief replace contents with min(count,N-1) copies of character ch **/ + constexpr flatstring & assign(size_type count, value_type ch) { + std::size_t pos = 0; + for (; pos < std::min(count, N-1); ++pos) + value_[pos] = ch; + for (; pos < N; ++pos) + value_[pos] = '\0'; + + return *this; + } + /** @brief replace contents with first N-1 characters of str **/ + constexpr flatstring & assign(const flatstring & x) { + for (std::size_t pos = 0; pos < N-1; ++pos) + value_[pos] = x.value_[pos]; + value_[N-1] = '\0'; + return *this; + } + /** @brief replace contents with substring [pos,pos+count] of str **/ + constexpr flatstring & assign(const flatstring & x, + size_type pos, size_type count = npos) { + std::size_t i = 0; + for (; + i < std::min(std::min(count, + std::max(x.capacity-1 - pos, + 0)), + N-1); + ++i) + value_[i] = x.value_[pos+i]; + for (; i < N; ++i) + value_[i] = '\0'; + + return *this; + } + /** @brief replace contents with range [cstr, cstr + count) **/ + constexpr flatstring & assign(const value_type * cstr, size_type count) { + std::size_t i = 0; + for (; i < std::min(N-1, count); ++i) + value_[i] = cstr[i]; + for (; i < N; ++i) + value_[i] = '\0'; + + return *this; + } + /** @brief replace contents with C-style string cstr **/ + constexpr flatstring & assign(const value_type * cstr) { + std::size_t i = 0; + const value_type * p = cstr; + while ((i < N-1) && (*p != '\0')) { + value_[i] = *p; + ++i; + ++p; + } + for (; i < N; ++i) + value_[i] = '\0'; + + return *this; + } + /** @brief replace contents with iterator range [first, last) **/ + template + constexpr flatstring & assign(InputIter first, InputIter last) { + InputIter ix = first; + std::size_t i = 0; + for (; (i < N-1) && (ix != last); ++i) { + value_[i] = *ix; + } + for (; i < N; ++i) + value_[i] = '\0'; + return *this; + } + ///@} + + // insert + // insert_range + // erase + // push_back + // append + // append_range + // operator+= + // replace + // replace_with_range + // copy + // find + // rfind + // find_first_of + // find_first_not_of + // find_last_of + // find_last_not_of + // compare + // starts_with + // end_with + // contains + // substr + + /** @defgroup flatstring-conversion-operators conversion operators **/ + ///@{ + /** @brief conversion to @c std::string + * + * Example + * @code + * constexpr flatstring s("bazinga!"); + * std::string s_str{s.str()}; + * @endcode + **/ + std::string str() const { return std::string(value_); } + + /** @brief conversion operator to string_view **/ + constexpr operator std::string_view() const noexcept { return std::string_view(value_); } + + /** @brief conversion operator to C-style string. + * + * Example + * @code + * constexpr flatstring s("obey gravity.."); + * strcmp(s, "obey..."); + * @endcode + **/ + constexpr operator const char * () const { return value_; } + ///@} + + private: + constexpr value_type & at_aux(size_type pos) { + if (pos >= N) { +#ifdef NOT_USING + /* note: can't build stringstream at compile time */ + std::stringstream ss; + ss << "flatstring<" << N << ">::at: expected pos=[" << pos << "] in interval [0," << N << ")" << std::endl; +#endif + + throw std::out_of_range("at_aux: range error"); + } + + return (*this)[pos]; + } + + template + constexpr Iterator last() { + Iterator p = &value_[N-1]; + + /* search backward for first padding '\0' */ + while ((p > &value_[0]) && (*(p-1) == '\0')) + --p; + + return p; + } + + public: + /** @defgroup flatstring-instance-variables instance variables **/ + ///@{ + + /** @brief characters comprising this literal string **/ + char value_[N]; + + ///@} + }; + + /** @brief sentinel type, for forbidden stringliteral with no space for a null terminator **/ + template <> + struct flatstring<0> { flatstring() = delete; }; + + // non-member functions + // erase + // erase_if + // operator<< + // operator>> + // getline + // stoi + // stol + // stoll + // stoul + // stoull + // stof + // stod + // stold + +#ifdef NOT_USING + /** @brief all_same_v is true iff types T1 = .. = Tn + **/ + template < typename First, typename... Rest > + constexpr auto + all_same_v = std::conjunction_v< std::is_same... >; +#endif + + /** @brief Concatenate native or wrapped string literals + * + * Can mix stringliteral objects and native C-style string literals, + * and still preserve constexpr-ness. + * + * Example: + * @code + * constexpr auto s = stringliteral_concat(stringliteral("hello"), + * ", ", + * stringliteral("world")); + * static_assert(s.capacity == 13); + * @endcode + * + **/ + template < typename... Ts> + constexpr auto + flatstring_concat(Ts && ... args) noexcept + { +#ifdef NOT_USING + static_assert(all_same_v...>, + "string must share the same char type"); + + using char_type = std::remove_const_t< std::remove_pointer_t < std::common_type_t < Ts... > > >; +#endif + using value_type = char; + + /** n1: total number of bytes used by arguments **/ + constexpr std::size_t n1 = (sizeof(Ts) + ...); + /** z1: each string arg has a null terminator included in its size, + * z1 is the number of arguments in parameter pack Ts, + * which equals the number of null terminators used + **/ + constexpr std::size_t z1 = sizeof...(Ts); + + /** n: number of chars in concatenated string. +1 for final null **/ + constexpr std::size_t n + = (n1 / sizeof(value_type)) - z1 + 1; + + flatstring result; + + std::size_t pos = 0; + + auto detail_concat = [ &pos, &result ](auto && arg) { + constexpr auto count = (sizeof(arg) - sizeof(value_type)) / sizeof(value_type); + + std::copy_n(/*arg.c_str()*/ static_cast(arg), count, result.value_ + pos); + pos += count; + }; + + (detail_concat(args), ...); + + return result; + } + + /** @brief compare two string literals lexicographically. + * + * Example: + * @code + * constexpr auto cmp = stringliteral_compare(stringliteral("foo"), stringliteral("bar")); + * static_assert(cmp > 0); + * @endcode + **/ + template + constexpr auto + flatstring_compare(const flatstring & s1, + const flatstring & s2) noexcept + { + return (std::string_view(s1.value_) <=> std::string_view(s2.value_)); + } + + /** @brief 3-way compare for two stringliterals **/ + template + constexpr auto + operator<=>(const flatstring & s1, + const flatstring & s2) noexcept + { + return flatstring_compare(s1, s2); + } +} /*namespace xo*/ + +/** end stringliteral.hpp **/ diff --git a/include/xo/flatstring/flatstring_iostream.hpp b/include/xo/flatstring/flatstring_iostream.hpp new file mode 100644 index 00000000..c2d7e738 --- /dev/null +++ b/include/xo/flatstring/flatstring_iostream.hpp @@ -0,0 +1,37 @@ +/** @file flatstring_iostream.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "flatstring.hpp" +#include +//#include + +namespace xo { + /** @brief print flatstring on stream os. + * + **/ + template + void + print_flatstring (std::ostream & os, const flatstring & x) { + os << x.c_str(); + } + + /** @brief print flatstring x on stream os. + * + * Example + * @code + * cout << flatstring("foo"); // outputs "foo" + * @endcode + **/ + template + inline std::ostream & + operator<< (std::ostream & os, const flatstring & x) { + print_flatstring(os, x); + return os; + } +} /*namespace xo*/ + +/** end flatstring_iostream.hpp **/ diff --git a/include/xo/stringliteral/string_view_concat.hpp b/include/xo/flatstring/string_view_concat.hpp similarity index 100% rename from include/xo/stringliteral/string_view_concat.hpp rename to include/xo/flatstring/string_view_concat.hpp diff --git a/include/xo/stringliteral/stringliteral.hpp b/include/xo/stringliteral/stringliteral.hpp deleted file mode 100644 index 72235727..00000000 --- a/include/xo/stringliteral/stringliteral.hpp +++ /dev/null @@ -1,96 +0,0 @@ -/** @file stringliteral.hpp - * - * Author: Roland Conybeare - **/ - -#pragma once - -#include -#include -#include - -namespace xo { - /** @class stringliteral - * - * @brief class to represent a literal string at compile time, for use as template argument - **/ - template - struct stringliteral { - constexpr stringliteral() { if (N > 0) value_[0] = '\0'; } - constexpr stringliteral(const char (&str)[N]) { std::copy_n(str, N, value_); } - constexpr int size() const { return N; } - - constexpr char const * c_str() const { return value_; } - - char value_[N]; - }; - - /** @brief all_same_v is true iff types T1 = .. = Tn - **/ - template < typename First, typename... Rest > - constexpr auto - all_same_v = std::conjunction_v< std::is_same... >; - - /** @brief concatenate string literals - * - * NOTE: this isn't constexpr in clang16 - **/ - template < typename... Ts> - constexpr auto - stringliteral_concat(Ts && ... args) - { -#ifdef NOT_USING - static_assert(all_same_v...>, - "string must share the same char type"); - - using char_type = std::remove_const_t< std::remove_pointer_t < std::common_type_t < Ts... > > >; -#endif - using char_type = char; - - /** n1: total number of bytes used by arguments **/ - constexpr size_t n1 = (sizeof(Ts) + ...); - /** z1: each string arg has a null terminator included in its size, - * z1 is the number of arguments in parameter pack Ts, - * which equals the number of null terminators used - **/ - constexpr size_t z1 = sizeof...(Ts); - - /** n: number of chars in concatenated string. +1 for final null **/ - constexpr size_t n - = (n1 / sizeof(char_type)) - z1 + 1; - - stringliteral result; - size_t pos = 0; - - auto detail_concat = [ &pos, &result ](auto && arg) { - constexpr auto count = (sizeof(arg) - sizeof(char_type)) / sizeof(char_type); - - std::copy_n(arg.c_str(), count, result.value_ + pos); - pos += count; - }; - - (detail_concat(args), ...); - - //return stringliteral(""); - return result; - } - -#ifdef NOT_USING - template - constexpr auto - stringliteral_compare(stringliteral && s1, stringliteral && s2) - { - return std::string_view(s1.value_) <=> std::string_view(s2.value_); - } -#endif - - template - constexpr auto - stringliteral_compare(const stringliteral & s1, const stringliteral & s2) - { - return std::string_view(s1.value_) <=> std::string_view(s2.value_); - } -} /*namespace xo*/ - - -/** end stringliteral.hpp **/ diff --git a/include/xo/stringliteral/stringliteral_iostream.hpp b/include/xo/stringliteral/stringliteral_iostream.hpp deleted file mode 100644 index 18a371a5..00000000 --- a/include/xo/stringliteral/stringliteral_iostream.hpp +++ /dev/null @@ -1,36 +0,0 @@ -/** @file stringliteral_iostream.hpp - * - * Author: Roland Conybeare - **/ - -#pragma once - -#include "stringliteral.hpp" -#include -//#include - -namespace xo { - /** @brief print stringliteral on stream os. - * - **/ - template - void - print_stringliteral (std::ostream & os, const stringliteral & x) { - os << x.c_str(); - } - - /** @brief print stringliteral x on stream os. - * - * Example - * @code - * cout << stringliteral("foo"); // outputs "foo" - **/ - template - inline std::ostream & - operator<< (std::ostream & os, const stringliteral & x) { - print_stringliteral(os, x); - return os; - } -} /*namespace xo*/ - -/** end stringliteral_iostream.hpp **/ diff --git a/utest/.#flatstring_utest_main.cpp b/utest/.#flatstring_utest_main.cpp new file mode 120000 index 00000000..a0c5445f --- /dev/null +++ b/utest/.#flatstring_utest_main.cpp @@ -0,0 +1 @@ +roland@roly-desktop-23.717424:1712774400 \ No newline at end of file diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..11e76f63 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,21 @@ +# xo-flatstring/utest/CMakeLists.txt + +set(SELF_EXE utest.flatstring) +set(SELF_SRCS + flatstring_utest_main.cpp + flatstring.test.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) +#target_code_coverage(${SELF_EXE} AUTO ALL) + +# ---------------------------------------------------------------- +# deps: logutils, ... + +xo_self_headeronly_dependency(${SELF_EXE} xo_flatstring) +xo_dependency(${SELF_EXE} indentlog) +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# end CMakeLists.txt diff --git a/utest/flatstring.test.cpp b/utest/flatstring.test.cpp new file mode 100644 index 00000000..1b5603a7 --- /dev/null +++ b/utest/flatstring.test.cpp @@ -0,0 +1,131 @@ +/** @file flatstring.utest.cpp **/ + +#include "xo/stringliteral/stringliteral.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tag.hpp" +#include +//#include + +namespace xo { + using namespace std; + + namespace ut { + template + void + flatstring_runtime_tests(const String & str, const char * text) { + INFO(tostr(XTAG(str), XTAG(text))); + + REQUIRE(str.fixed_capacity == strlen(text)+1); + REQUIRE(str.capacity() == strlen(text)); + REQUIRE(str.size() == strlen(text)); + REQUIRE(str.length() == strlen(text)); + REQUIRE(strcmp(str.c_str(), text) == 0); + REQUIRE(strcmp(str, text) == 0); + + /* verify range iteration visits contents in order */ + { + size_t i = 0; + for (char ch : str) { + INFO(XTAG(i)); + + CHECK(ch == text[i]); + + ++i; + } + } + } + + /* using macro here because template argument depends on size of literal C string, + * and we can't use such a string as a template argument. + * + * static_asserts: using these to verify that constexpr methods are being computed + * at compile time. + * + * REQUIRE() calls to do verification that relies on non-constexpr calls such as + * strlen(), strcmp() + */ +# define LITERAL_TEST_BODY(name, text) \ + constexpr flatstring name{text}; \ + static_assert(name[0]==text[0]); \ + static_assert(name.at(0)==text[0]); \ + static_assert(name.empty() == true || name.empty() == false); \ + static_assert(name.capacity() >= 0); \ + static_assert(name.begin() != nullptr); \ + static_assert(name.end() != nullptr); \ + static_assert(name.cbegin() != nullptr); \ + static_assert(name.cend() != nullptr); \ + static_assert(name.rbegin() != nullptr); \ + static_assert(name.rend() != nullptr); \ + static_assert(name.crbegin() != nullptr); \ + static_assert(name.crend() != nullptr); \ + static_assert(name.size() >= 0); \ + static_assert(name.c_str() != nullptr); \ + static_assert((name <=> name) == 0); \ + static_assert(name == name); \ + static_assert(name >= name); \ + static_assert(name <= name); \ + static_assert(!(name != name)); \ + static_assert(!(name > name)); \ + static_assert(!(name < name)); \ + flatstring_runtime_tests(name, text); \ + REQUIRE(name.fixed_capacity == strlen(text)+1); \ + REQUIRE(name.capacity() == strlen(text)); \ + REQUIRE(name.size() == strlen(text)); \ + REQUIRE(name.length() == strlen(text)); \ + REQUIRE(strcmp(name.c_str(), text) == 0); \ + REQUIRE(strcmp(name, text) == 0); \ + static_assert(string_view(name) == string_view(name)); \ + + + + TEST_CASE("flatstring", "[flatstring][compile-time]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.flatstring")); + //log && log("(A)", xtag("foo", foo)); + + /* mostly compile-time tests here */ + + LITERAL_TEST_BODY(s1, "h"); + LITERAL_TEST_BODY(s2, "he"); + LITERAL_TEST_BODY(s3, "hel"); + LITERAL_TEST_BODY(s4, "hell"); + LITERAL_TEST_BODY(s5, "hello"); + LITERAL_TEST_BODY(s6, "hello,"); + LITERAL_TEST_BODY(s7, "hello, "); + LITERAL_TEST_BODY(s8, "hello, w"); + LITERAL_TEST_BODY(s9, "hello, wo"); + LITERAL_TEST_BODY(s10, "hello, wor"); + LITERAL_TEST_BODY(s11, "hello, worl"); + LITERAL_TEST_BODY(s12, "hello, world"); + LITERAL_TEST_BODY(s13, "hello, world!"); + + static_assert(s1 != s2); + static_assert(s2 != s3); + static_assert(s3 != s4); + static_assert(s4 != s5); + static_assert(s12 != s13); + + static_assert(s1 < s2); + static_assert(s2 < s3); + static_assert(s3 < s4); + static_assert(s4 < s5); + static_assert(s12 < s13); + + static_assert(s2 > s1); + static_assert(s3 > s2); + static_assert(s4 > s3); + static_assert(s5 > s4); + static_assert(s13 > s12); + } /*TEST_CASE(flatstring)*/ + + } /*namespace ut*/ +} /*namespace xo*/ + +/** end flatstring.utest.cpp **/ diff --git a/utest/flatstring_utest_main.cpp b/utest/flatstring_utest_main.cpp new file mode 100644 index 00000000..e7a2c140 --- /dev/null +++ b/utest/flatstring_utest_main.cpp @@ -0,0 +1,6 @@ +/* @file flatstring_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end flatstring_utest_main.cpp */ From eca4267636808973d97c9b08a6ee95a100f6df44 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 17:26:19 -0400 Subject: [PATCH 0604/2524] xo-flatstring: + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..13c0afb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json From f7c633be86bbe61c650a00c8d6ec580009637f26 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 17:44:55 -0400 Subject: [PATCH 0605/2524] xo-flatstring: ++ doc improvements --- docs/flatstring-class.rst | 5 ++++ docs/flatstring-functions.rst | 3 +- include/xo/flatstring/flatstring.hpp | 42 ++++++++++++++++++++-------- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/docs/flatstring-class.rst b/docs/flatstring-class.rst index d2c9c546..3e34cd4c 100644 --- a/docs/flatstring-class.rst +++ b/docs/flatstring-class.rst @@ -17,6 +17,11 @@ Instance Variables .. doxygengroup:: flatstring-instance-variables :content-only: +Types +----- + +.. doxygengroup:: flatstring-types + Constants --------- diff --git a/docs/flatstring-functions.rst b/docs/flatstring-functions.rst index 7e3c1fb1..9106a2b5 100644 --- a/docs/flatstring-functions.rst +++ b/docs/flatstring-functions.rst @@ -12,4 +12,5 @@ Flatstring Functions .. doxygenfunction:: xo::flatstring_concat -.. doxygenfunction:: xo::flatstring_compare +.. doxygengroup:: flatstring-3way-compare + :content-only: diff --git a/include/xo/flatstring/flatstring.hpp b/include/xo/flatstring/flatstring.hpp index 5a68bd6a..55d9f1fd 100644 --- a/include/xo/flatstring/flatstring.hpp +++ b/include/xo/flatstring/flatstring.hpp @@ -29,20 +29,30 @@ namespace xo { **/ template struct flatstring { - /** @defgroup flatstring-types template types **/ + /** @defgroup flatstring-types template types + * @brief Template types exposed by @c flatstring + **/ ///@{ + /** @brief character traits for this flatstring **/ using traits_type = std::char_traits; + /** @brief type of each character in this flatstring **/ using value_type = char; using allocator_type = std::allocator; using size_type = std::allocator_traits::size_type; using difference_type = std::allocator_traits::difference_type; + /** @brief type of a character reference **/ using reference = value_type &; + /** @brief type of a readonly character reference **/ using const_reference = const value_type &; using pointer = std::allocator_traits::pointer; using const_pointer = std::allocator_traits::const_pointer; + /** @brief representation for a read/write iterator **/ using iterator = char *; + /** @brief representation for a readonly iterator **/ using const_iterator = const char *; + /** @brief representation for a read/write reverse iterator **/ using reverse_iterator = char *; + /** @brief representation for a readonly reverse iterator **/ using const_reverse_iterator = const char *; ///@} @@ -129,6 +139,7 @@ namespace xo { ///@{ constexpr iterator begin() { return &value_[0]; } constexpr iterator end() { return this->last(); } + constexpr const_iterator cbegin() const { return &value_[0]; } constexpr const_iterator cend() const { return const_cast(this)->last(); } constexpr const_iterator begin() const { return cbegin(); } @@ -329,16 +340,13 @@ namespace xo { all_same_v = std::conjunction_v< std::is_same... >; #endif - /** @brief Concatenate native or wrapped string literals - * - * Can mix stringliteral objects and native C-style string literals, - * and still preserve constexpr-ness. + /** @brief Concatenate flatstrings, possibly mixed with C-style char arrays * * Example: * @code - * constexpr auto s = stringliteral_concat(stringliteral("hello"), - * ", ", - * stringliteral("world")); + * constexpr auto s = flatstring_concat(flatstring("hello"), + * ", ", + * flatstring("world")); * static_assert(s.capacity == 13); * @endcode * @@ -383,11 +391,11 @@ namespace xo { return result; } - /** @brief compare two string literals lexicographically. + /** @brief compare two flatstrings lexicographically. * * Example: * @code - * constexpr auto cmp = stringliteral_compare(stringliteral("foo"), stringliteral("bar")); + * constexpr auto cmp = flatstring_compare(stringliteral("foo"), stringliteral("bar")); * static_assert(cmp > 0); * @endcode **/ @@ -400,15 +408,25 @@ namespace xo { return (std::string_view(s1.value_) <=> std::string_view(s2.value_)); } - /** @brief 3-way compare for two stringliterals **/ + /** @defgroup flatstring-3way-compare 3way-compare **/ + ///@{ + /** @brief 3-way compare for two flatstrings + * + * Example + * @code + * constexpr auto cmp = (flatstring("foo") <=> flatstring("bar")); + * static_assert(cmp != 0); + * @endcode + **/ template constexpr auto operator<=>(const flatstring & s1, const flatstring & s2) noexcept { - return flatstring_compare(s1, s2); + return (std::string_view(s1) <=> std::string_view(s2)); } + ///@} } /*namespace xo*/ /** end stringliteral.hpp **/ From ecb321f0f18bfc8084e333becb9a9433d64d5681 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 21:37:48 -0400 Subject: [PATCH 0606/2524] tidy: cleanup stray stringlit -> flatstring naming --- CMakeLists.txt | 2 +- docs/CMakeLists.txt | 8 ++++---- docs/conf.py | 2 +- example/ex1/CMakeLists.txt | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 79d62595..af27a769 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,7 @@ add_subdirectory(docs) # ---------------------------------------------------------------- # provide find_package() support for projects using this library -set(SELF_LIB xo_stringliteral) +set(SELF_LIB xo_flatstring) xo_add_headeronly_library(${SELF_LIB}) xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 0751be74..c6fcc37b 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -1,4 +1,4 @@ -# xo-stringliteral/docs/CMakeLists.txt +# xo-flatstring/docs/CMakeLists.txt if (XO_SUBMODULE_BUILD) # in submodule build, rely on toplevel docs/CMakeLists.txt file instead @@ -6,8 +6,8 @@ else() # build docs starting from here only in standalone build. # otherwise use top-level doxygen setup instead. - set(ALL_LIBRARY_TARGETS xo_stringliteral) # todo: automate this from xo-cmake macros - set(ALL_UTEST_TARGETS xo_stringliteral_ex1 ) # todo: automate this from xo-cmake macros + set(ALL_LIBRARY_TARGETS xo_flatstring) # todo: automate this from xo-cmake macros + set(ALL_UTEST_TARGETS xo_flatstring_ex1 ) # todo: automate this from xo-cmake macros # look for doxygen executable find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED) @@ -24,7 +24,7 @@ else() set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html) - # .hpp files reachable from xo-stringliteral/include + # .hpp files reachable from xo-flatstring/include # # REMINDER: for reliability will need to re-run cmake when the set of .hpp files changes # diff --git a/docs/conf.py b/docs/conf.py index 493bc187..26dc0ec0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -6,7 +6,7 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = 'xo stringlit documentation' +project = 'xo flatstring documentation' copyright = '2024, Roland Conybeare' author = 'Roland Conybeare' diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt index abe34974..c026a159 100644 --- a/example/ex1/CMakeLists.txt +++ b/example/ex1/CMakeLists.txt @@ -1,6 +1,6 @@ -# xo-stringliteral/example/ex1/CMakeLists.txt +# xo-flatstring/example/ex1/CMakeLists.txt -set(SELF_EXE xo_stringliteral_ex1) +set(SELF_EXE xo_flatstring_ex1) set(SELF_SRCS ex1.cpp) add_executable(${SELF_EXE} ${SELF_SRCS}) @@ -9,7 +9,7 @@ xo_include_options2(${SELF_EXE}) # ---------------------------------------------------------------- # dependencies.. -xo_self_dependency(${SELF_EXE} xo_stringliteral) +xo_self_headeronly_dependency(${SELF_EXE} xo_flatstring) #xo_dependency(${SELF_EXE} reflect) # end CMakeLists.txt From c9fa89bbdde6f48f5fe4cd6b40da7c82968832f9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 21:38:27 -0400 Subject: [PATCH 0607/2524] xo-flatstring: build: + GNUInstallDirs --- CMakeLists.txt | 1 + cmake/xo-bootstrap-macros.cmake | 2 +- docs/CMakeLists.txt | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index af27a769..72b0d161 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ project(xo_flatstring VERSION 1.0) enable_language(CXX) # common XO cmake macros (see proj/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) # ---------------------------------------------------------------- diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 83f4a39b..1542d355 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -15,7 +15,7 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL pre endif() if (NOT XO_SUBMODULE_BUILD) - message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}") + message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_DOCDIR=${CMAKE_INSTALL_DOCDIR} -B ${CMAKE_BINARY_DIR}") message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") endif() diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index c6fcc37b..b31549e0 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -94,12 +94,15 @@ else() # - html docs generated in build/docs/sphinx # - copy the doc tree to share/doc/xo_unit/html # + # DESTINATION: CMAKE_INSTALL_DOCDIR + # => DATAROOTDIR/doc/PROJECT_NAME + # => CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring # OPTIONAL: install directory tree if it exists, # but don't complain if it's missing install( DIRECTORY ${SPHINX_OUTPUT_DIR} FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ - DESTINATION ${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME} + DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT Documentation OPTIONAL) From b2f2272b32233fa5bd33a0f78c21dcb9598a7b40 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 21:38:59 -0400 Subject: [PATCH 0608/2524] xo-flatstring: ++ README --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 856b61fb..35b6f731 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,15 @@ Features: - char array representation with maximum size set at compile time. - compile time construction from char array and string concatenation - pointer-free implementation, instances can be used as template arguments -- To the extent practical, provides the same api as `std::string` +- To the extent practical, provides the same api as `std::string`: includes iterators, + access methods, assignment, conversion operators. Limitations: - requires c++20 - not resizable. - does not support wide characters. +- (asof April 2024) missing features: `insert`, `erase`, `push_back`, `append`, `replace`, + `find`, `compare`, `starts_with`, `ends_with`, `contains`, `substr`. ## Getting started From eb11089c4a9739e6e2547e46e4360d60386eb26b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 16 Apr 2024 21:39:13 -0400 Subject: [PATCH 0609/2524] xo-flatstring: + LICENSE --- LICENSE | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cae3cb5d --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2024 Roland Conybeare , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. From 5b9fc80258b2a5e55f9f8d00154ee7178a4964cc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 10:56:47 -0400 Subject: [PATCH 0610/2524] xo-flatstring: docs: + source code link + tweaks --- docs/install.rst | 56 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/docs/install.rst b/docs/install.rst index aab059ab..faa72baf 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -3,24 +3,46 @@ .. toctree :maxdepth: 2 -Install -======= +Source +====== -`xo-flatstring source`_ lives on github. +Source code lives on github `here`_ -.. _xo-flatstring source: https://github.com/rconybea/xo-flatstring +.. _here: https://github.com/rconybea/xo-flatstring -Implementation relies on c++20 features (for example class-instances as template arguments). -Tested with gcc 13.2 - -Include as submodule --------------------- +To clone from git: .. code-block:: bash - cd myproject - git submodule add -b main https://github.com/rconybea/xo-flatstring ext/xo-flatstring - git submodule update --init + git clone https://github.com/rconybea/xo-flatstring + +Implementation relies on c++20 features (expanded use of constexpr; class-instances as template arguments). +Tested with gcc 13.2 + +Install +======= + +Since xo-flatstring is header-only, can incorporate into another project just by copying the include directories +to somewhere convenient. + +Copy includes +------------- + +.. code-block:: bash + + # For example.. + cd myproject + mkdir -p ext/xo-flatstring + rsync -a -v path/to/xo-flatstring/include/ ext/xo-flatstring/ + +Include as git submodule +------------------------ + +.. code-block:: bash + + cd myproject + git submodule add -b main https://github.com/rconybea/xo-flatstring ext/xo-flatstring + git submodule update --init This assumes you organize directly-incorporated dependencies under directory ``myproject/ext``. You would then add ``myproject/ext/xo-flatstring/include`` to your compiler's include path, @@ -28,7 +50,7 @@ and from c++ do something like .. code-block:: c++ - #include + #include in c++ source files that rely on xo-flatstring @@ -54,3 +76,11 @@ Unit test dependencies: .. _catch2: https://github.com/catchorg/Catch2 .. _xo-cmake: https://github.com/rconybea/xo-cmake .. _xo-indentlog: https://github.com/rconybea/indentlog + +To build documentation, will also need: + +* `doxygen` +* `graphviz` +* `sphinx` +* `breathe` +* `sphinx_rtd_theme` From 02bb6bd82654468b74e29f51068bb0874e708396 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 10:57:11 -0400 Subject: [PATCH 0611/2524] xo-flatstring: bugfix: include path in utest --- utest/flatstring.test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/flatstring.test.cpp b/utest/flatstring.test.cpp index 1b5603a7..877a8183 100644 --- a/utest/flatstring.test.cpp +++ b/utest/flatstring.test.cpp @@ -1,6 +1,6 @@ /** @file flatstring.utest.cpp **/ -#include "xo/stringliteral/stringliteral.hpp" +#include "xo/flatstring/flatstring.hpp" #include "xo/indentlog/scope.hpp" #include "xo/indentlog/print/tag.hpp" #include From c9c46c24a6b004ba602b935a1738391a20f14e09 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 10:57:35 -0400 Subject: [PATCH 0612/2524] xo-flatstring: build: targets to install ccov output --- README.md | 11 ++++++++--- utest/CMakeLists.txt | 31 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 35b6f731..730c7a18 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,11 @@ Limitations: - (asof April 2024) missing features: `insert`, `erase`, `push_back`, `append`, `replace`, `find`, `compare`, `starts_with`, `ends_with`, `contains`, `substr`. +## Documentation + +xo-flatstring documentation here: https://rconybea.github.io/web/xo-flatstring/html/index.html +unit test coverage here: https://rconybea.github.io/web/xo-flatstring/ccov/html/index.html + ## Getting started ### build + install @@ -39,18 +44,18 @@ When complete, point local browser to `xo-flatstring/.build/docs/sphinx/index.ht ``` $ cd xo-flatstring $ mkdir .build-ccov -$ cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug -B .build-ccov +$ cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DCMAKE_BUILD_TYPE=coverage -B .build-ccov $ cmake --build .build-ccov ``` run coverage-enabled unit tests ``` -$ (cd .build-ccov && ctest) +$ cmake --build .build-ccov -- test ``` generate html+text coverage report ``` -$ .build-ccov/gen-ccov +$ cmake --build .build-ccov -- ccov ``` browse to `.build-ccov/ccov/html/index.html` diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 11e76f63..142a1a4b 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -11,6 +11,37 @@ xo_include_options2(${SELF_EXE}) add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) #target_code_coverage(${SELF_EXE} AUTO ALL) +# ---------------------------------------------------------------- +# in coverage build, target to build+install coverage report + +set(CCOV_OUTPUT_DIR ${PROJECT_BINARY_DIR}/ccov/html) +set(CCOV_INDEX_FILE ${CCOV_OUTPUT_DIR}/index.html) +set(CCOV_REPORT_EXE ${PROJECT_BINARY_DIR}/gen-ccov) +set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov) + +# 'test' target should always be out-of-date +# +# DEPENDS: reminder - can't put 'test' here, requires 'all' target +# +add_custom_command( + OUTPUT ${CCOV_INDEX_FILE} + DEPENDS ${SELF_EXE} + COMMAND ${CCOV_REPORT_EXE} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Generating coverage report -> [${CCOV_OUTPUT_DIR}]") + +add_custom_target( + ccov + DEPENDS ${CCOV_INDEX_FILE} ${SELF_EXE}) + +# OPTIONAL: quietly skip this step if ccov report not generated +install( + DIRECTORY ${CCOV_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CCOV_INSTALL_DOCDIR} + COMPONENT Documentation + OPTIONAL) + # ---------------------------------------------------------------- # deps: logutils, ... From e171c28701d9c3223a644aeb98532fb4bb8a495f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 10:59:53 -0400 Subject: [PATCH 0613/2524] xo-flatstring: README: tidy doc links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 730c7a18..7e90d6b1 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ Limitations: ## Documentation -xo-flatstring documentation here: https://rconybea.github.io/web/xo-flatstring/html/index.html -unit test coverage here: https://rconybea.github.io/web/xo-flatstring/ccov/html/index.html +- xo-flatstring documentation here: [documentation][https://rconybea.github.io/web/xo-flatstring/html/index.html] +- unit test coverage here: [coverage][https://rconybea.github.io/web/xo-flatstring/ccov/html/index.html] ## Getting started From 85fa69c58ccbea410817d2f9401160fccb9b3a50 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 11:02:21 -0400 Subject: [PATCH 0614/2524] xo-flatstring: README: fix links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7e90d6b1..7f136f0e 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ Limitations: ## Documentation -- xo-flatstring documentation here: [documentation][https://rconybea.github.io/web/xo-flatstring/html/index.html] -- unit test coverage here: [coverage][https://rconybea.github.io/web/xo-flatstring/ccov/html/index.html] +- xo-flatstring documentation here: [documentation](https://rconybea.github.io/web/xo-flatstring/html/index.html) +- unit test coverage here: [coverage](https://rconybea.github.io/web/xo-flatstring/ccov/html/index.html) ## Getting started From 0cc317b140d444d88a25edd4af2c7177cb3a0478 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 12:36:44 -0400 Subject: [PATCH 0615/2524] xo-flatstring: build docs --- README.md | 3 +++ utest/CMakeLists.txt | 3 +++ 2 files changed, 6 insertions(+) diff --git a/README.md b/README.md index 7f136f0e..0e7ca89b 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,9 @@ $ cmake --build .build-ccov -- ccov browse to `.build-ccov/ccov/html/index.html` +Running `cmake --install` for a coverage build will install coverage report (if generated) +to `${CMAKE_INSTALL_PREFIX}/share/doc/xo_flatstring` + ### LSP support ``` $ cd xo-flatstring diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 142a1a4b..091e56ef 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -17,6 +17,9 @@ add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) set(CCOV_OUTPUT_DIR ${PROJECT_BINARY_DIR}/ccov/html) set(CCOV_INDEX_FILE ${CCOV_OUTPUT_DIR}/index.html) set(CCOV_REPORT_EXE ${PROJECT_BINARY_DIR}/gen-ccov) +# CMAKE_INSTALL_DOCDIR +# =default=> DATAROOTDIR/doc/PROJECT_NAME +# =default=> CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov) # 'test' target should always be out-of-date From ff7afb81762b9c3939eef9ab8a3c610ad2149b96 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 12:36:59 -0400 Subject: [PATCH 0616/2524] xo-flatstring: + img/icon.svg --- img/icon.svg | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 img/icon.svg diff --git a/img/icon.svg b/img/icon.svg new file mode 100644 index 00000000..f86f334f --- /dev/null +++ b/img/icon.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + From e0960d984822c2d297634385d846bb66c02fe1ba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 12:52:28 -0400 Subject: [PATCH 0617/2524] xo-flatstring: + icons --- img/favicon.ico | Bin 0 -> 303879 bytes img/xo-icon.svg | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 img/favicon.ico create mode 100644 img/xo-icon.svg diff --git a/img/favicon.ico b/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f4e9d76070462c14b4ebab0154ce19516d899be9 GIT binary patch literal 303879 zcmZQzU}WH800Bk@1%{bI3=GQ{7#I#50EsIwXaq4a*b6f-G&Df@9E=R4iLVE69a=e6AOcZ0ffIom4PANg@r*w1H#XcVqjp;Vqs8FfbdrcFfin| zurNqSK=>V;3=HB^Sr`NaAp8s#28Q|bSQt73{M>oDq(Bbk_4IHF0?C3f2O9$eLrl?% zGzJEt1Wy;okcwMx?^e!`4V`)Xl_lGEhwlA+eCPCc z4&UzVyITAEd*!^}A1=JSvnqJy=WCYB?-lcPkPl9*+qlKwp>6$A`&P~04ra`OB6-Vv z*v=?;E)Z(J-<_;6E%6U$=KPI&rZ6?UnY25~H6 zIn!cc$jS@3$BucOS@w+S^IYo)}#xrt{ zB_7y!AVVR)ZKp6ZYl8BIOLOg2&+RQdb7^|cb+uiO zm6#Xz%&Dh~I6s8-e6!tIxawd+>Z6$Wbp^R*hj;B`>anzYd#TTci(!-R-Omdnl+2ek z*Pn6uV{MR{KJT0LY1#7U=2K^Qf6BGl^@r2qOV1W>+f5n_C&F^~us6iGTZ^YD=@~Ch zHt2iYp#Ep2%x8bywK|)WFYEn0A+)rX$;jkyo}$^Vx%=}6}~>DeHq7I9JHI)dj3sLTer61&Ml|yCuuO8IQ>Pequ?ID z%%v5{vl=DeXJ$XV>VI$6H&F>o5r!wKJuf#)rVB2=T6`d1-SlxX-xtVzLo)`{H{AJeE%98hDIY8=g&LJjVE&@p40R z<9^0(>M9?5)I)`n+E2UH#yJkWTMkzlp( zQ2+C%vHO%RczaxbYph`va;#*-o!*tx+!z|VTvaq<_Y1l+Cj24v=KeKFIDb_yMxUz1SME)-_=}FOb{AY6*-V6gF@~aM}1)T*}{F6xx0-P0q*AvqB_!ep}x$OV3Fk zeyb?vJxO#oVr_V+%EZThFYxb~7V-PD?VVSADQEo5>Ho(!`u8LsmXF?2H~FTVV&Ht^ zou8^|e@;?8nYUK||MMe1zb^U6Yx9U>IfH>sO2c)A_w45w;v)Z6ihrnnAl}mT-(q3> z{@9OvRX4u>m+G^bF+bgCj-=#r^F^W${0#keBz?8AT0Zw$12flxoea)%8QqzlGo&+B zhu7cZyu)?JCF4=~_1_0hItOSO8&7}iKGCo^!0Y){A-5@wJ3U*^Pu2diRnAz$ee&7> zr3)9f%71WuF#W)Bj;a6G9|&&#-#9h{@#2eZ7B79Y;_U-xiV^ZjG18_x?zS)@L& zKCqwhwN&i2i5`pz83qqh9uys_u4xcG5Z+k1bcoVFFx zU|6!A^@qU1{rmL=n71=bXX?Iex3jV89lv+O^WqD4z1OXs`j@wlq3^(qG)41;%^$8U zdU1|hVs+s()pL_n9_s&`xu#p5o4aS5y!1Zp&fXh;GahLD+HGsE+EZ*ZyKJW`+i8hI z!TZ|{bhdR0a2#7&n{>PQ>hlHP`vWQs)`-37Z&R=RUQ=|Gb<5W~{?!i|qr(4n_2@I# ztNh7JWX#!@zdPP`@`Te5uAcwi{%fni%~;{a*Nt2c)8aYzOi!FKd*c)J4G+IxFqZG) zJ`~l|%AW8x|L`NF{ySSf-%mZ3-Jp6kJ4lh`#4cW&E*{p&4e|nPU*{NXs`t93K6RVt zrV6wW-GoNGPFpnKyr z(TeO_>MEP8doJ|f_1Q6VKJ&eK-#mGl-8g1#eRnC>pu&4fH_x=ioZST$7uaJuo+Z9n zv$DmIL4A*&t?64~p_`(@jn*&TpN{j6X!bAbV~kHaeTU(#tisC|iY1Sdf8Aah{X1mx zH)SW`ZDudFH%xf1zx$a+g6%`8bxeG|zguP<-QaEI>1}@1baP{nhtJLSfSKRb_gt5M zDo_~_Q_Q^K7n?>M*S#-J5fz(RVzM?E&E0C&zIAEKgG#N$cLzVVOwnI8`Xtg6m{699ID#%ba_By`n3be<_G1^w`)H?xTEFY75kf&6F+8dd&8D; z$xb~pX=~RmkwrphydKZVN+z+L|t%p!lo1UpKJ4TXQ|Q-r6xCb)^(b z%5SB^4eu}6^A#lCdVYmxN{>+kv#Nw>>-Tf{83(d7f2Ti?PPOo@wD*Ud8w>)!J2#DT92Wjk1By_vyufI;ln)XdOB8-u^T-{^aF=^-|@=VvqL2OD4D z-M&=Mdu3+khRPx?>3SaNKEs43_D0ouZ-c=uiF&B1@;o#?$G+{?*4tAobXz(d7&bn> zWwL}%gkjqjo(h%L$LqdtxVSv$cEqpkuJ6m18^09>X=GMe82|IW+@?n9MZZq`ujKJI zVcHS=X$<9@6Xl&ZJ=Z9K@9$@Zf5Ll>rXCE4KDU~QUG)QZ`;vR2^`<{`H$3FD z5Pq;|3%Bi;-sSb@4=_c^#jpkIa;R*Wez@MkCVKg^efPQlS$*)?F>$~9|7kZii6&d~ z91=>{s(SUmlLY?+s|&Llq7z$O{~l{?)zD|nEB~R9=)0@7eCylD+~jK*ZR$ROmdlky>|AW+zL;Z=^bXaxpG>$}8|DVwKCA3sk^ky~RBLz; zyFeK0Jf4z@9rIWZO$uGDo0EO@<%XTxdd}WF;D6|PTBAAhd_|MO-VZ6i&%uJoeLu7yh!0QS>$XmpX*}E2fjOJJGma5Z(h$NSN>h}dd%gbMb)3) zzIqz(lF_=>e|OATC&mQvtH;>OOdFG|#jA`yI6jDZaNyPXX^i*T@>JF|+w0W6*`wrA zd4^jyqRh-?`Qx%ZhfR!+eT$#=Z}sDUkA6>x`*4-t_Q%s{PfzF1*|hWZ%OycC|9&|t z{@dKjdb&aWy)#?7uZxHzBir0o*|EIp4aX<*01+` zKGpK%uNU{U|JH}k9eq==;PA%S9WUpobZUNCn|zsD`_$UC)wQK>ZZxkullJ+1Wl+Mq z&|3+a{+bLtx{`;=9?Glc$VEl}s%^7ReN>)^;z>D6hAYo`BoMj$RIJRsD;oR4Z-hx4rL!P7iLpD-6Gl3deW<^aXN&He zK7R2lypG}U8MoxJAgAo~@b7I$m&=qZ@$1Z*UiPtFbt^+#|KA34p-t@l$}B7#KUw>k z$_3Lz^w_RleDLOx_rIl+=N&r2IpM!z+H$i;*NVRTZGNJ2!p5L{UEb5X3&jq6xafLA z=yiHa*g-F+@WlKF3J+Kw#2&cKvHRsUCYMGwo%vsH_HViM`23!a&JiVzyIq?R& zKF094=TI)USY73bzHXjWuGxogOFZCskn%v~fycHO{d#7_`3Z&}3=%I++%I%*@%Mn^ zVTV|sfA1}i$}8V~vvY?tT z*Bmp8FLSIswtnruy2cGVk9j#IL@?TLmI$$lmg?KTF<<&bi=w|_jy}No}G2rKJ)9tDIC?yuPHgQozSoPFkAinoXSLZ5z}qq zFY{)ownuF-Tf`Z>f_*lVL-DG&3x3-u)=oQ-asK%t&(MG^atk>yF`@#-SWoo$6TJ1CG%N@>>U*9pSedT*)__uC(&C*Dn8JNscm}vu7Bt8`6r%6g~eJfFFn7%s3nMD!mgdqX7MR(zPIK_ zp~t$i?@qRd9Wuf!cKl~tU$bGY?)DEqpU?N$edF*friLq1BiYoWo_qcbejqNfPsB!r z?~R+ui~rSqGuO92c^&Yt{YG*3wRN%Q`0FbIZn?@`n_{Eb{AkZ-uar%eHk$GrGru?P z<+hMH(YRNFC(bH=!8Q($|MRUK)ZdjSoGe$mW-su2NyVKN={x^qufOwCc+%P+h6#?v z@9gsxCd>LdP3!oyZ-OPCh2-|d2FnB{^+XjYu^V%Hx?0?oIM3u=u%CVPowuHuJ6+#y zym)x~vp&z=8NSYp4s3US%D-kk%@jE=D&J>rgj{yo)pofrW-#{VN|3%ADIK}T zOjy)x%H)fpi+j}1t$1^{Cih(QB|X33K!yozyIwq&)(CS;G&5LO@E}*>TcA<5;cA10 z$-L8RUH{eyjspB4F!A8`|4r-8T~OoGSPdF1^K|udS?83{qyZW%V_;xtU<3_EDS)IH z7#x@w7*ZG@7$P!Cj)uT!2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeD zjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mk zz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By z2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1J zhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kin zXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeD zjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mk zz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By z2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1J zhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kin zXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeD zjE2By2#kinXb6mkz-S1JhQMeDjE2A{7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C z(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R z7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7 zfzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu;ss)WGPyh4T_ z**Oec%g$!_pPdb+L2Qutubdo)A33>Hv2WCn(GVC6A@D5=l-?m}{aa2h z+u!VLslVAdMt`%jo&RTNd;iVOcKe&1ZT>eqTj5tu4i_v>{LIc~xKP_V7`!m*hS3lp zIRw6EWPr>1>)CmXf3maH|7B>N;DVfdMuNwVFe8b(84AVc6|RyM=`oE(NfSy}S`va{Pk zY5RY6_J0!an}6Bai+*S482>LTVF2Z&f%MO)-J>BuYzVy0$!GXqmCo=dJICpNcJ^r! z(mY1~_?Mmi@Naf@^5?WP)^DTr0I^Xps%`*7;D2s5!=LPImw(yW4=73dNP0j2&C1I8 zn4ZD(uBdbXeKu6Q>wo6ta$>b~RB$u|CV3=9l>3=9n73=9kkjEqd0EX*th3=9mW3=9kw3=9mG3=9lrdLp7$?^06E(jxu; z&(41NJ3B`Q(}ANRqai?f2(WVTu`)0+iZC!RfYO*J0|NtS;IV^&fnhNN1H*0x28MGC z3=DS|7#N;0FfhDfWMurnz`*dCfq~%*6o2MrWBYVGB;@b^oE(DXIT?B6=ilsXkh>`N z?5GK&A;7@E!@$6x#lXN2!oa}L%E-vLk%58X8Uq8vI|c@Zp9~BP-x(Mf-ZC&S++|>3 zIK{xgunn3QCPUR%F)%O`F)%RXL2;3euyDuo#Kd>BO8y3=FRs7#MyrFfe>#U|_hxz`(GXfq|i$fq@}~fq}u9fq_Arfq_Ai zfq{XSfq{V)djkpKn*aU%p!wUQWR&g1)E6)QWM_l&E5gE2)@TUe2cWjQ90LPG6twKV z2Tkvwb_=LIGn0XVAqm=!7lWp8{3ha&{hXP__%A!V>VJ0jKWgWJf7#hwcm&b~{f zJn-Rfc6Qp=j7+At$;rbu;zm72Q3!zA=%9Y+c?JfCzYGiv&lwmPW-~A_fa(CyxFtn~ zlcM=cdOE}ZoGj4VAKQP~*~iJs1EBGvoSgsvva|2~&CZVfl%2!!BQuK>XN;;HG9mDv zfq?-X|6^ca0AZLoPS1eaFsTd-44`xmQv85{fuW6ofkBsnfq@C9E*eXG&dvd^fBT)C zE&VS$yYXLk_HAPO1YgtB|KEs;{(mDndeh&mEWQ7w#SGuFb7<`1QRC?w0$&*z7(h*O zP?CjXb_Rz3d<+c#1sE9q@h~v_V@1supkW$#k_Jt_g2n>(K+Aj3m^^5#UY)-Fz-`;x z)KpMkfZ=~fJ&0ENo1LBhFFSkXzwGQQ|Fg4S|Ig0;0$l_B4Z0rs_V28$ZK>MYm!-J5 z|1&c(&H?4&cj+0pEg6*=Vj%!e>!9?{$-wa6n1SJcHUq=|Squ#ScQP>iKgq!G{|p1e z|6>dc|2Hu({O@OA_#eZ-@L!FA;TJOl19<(HJOcy61O^6%kI?r00tN;KBSK@70~JtT zva&$^0q|b0&so_lzq7N&{$%Iq{LRj>1+A0&o1N|OH!I8VPga)n*R(XyJTqvl3Y6KO zLgyz2%ITx_QZ)oX)1=_^&&|N_Ka_#t-xdaj|8E!={{JU{|1vQAf5^b_cQykH>36pg z3bX;3PYbtV^1nqvk9*j9RsS<%T~BiI^HRQ@w#VEBKY^mLD2KfM3T!0>G~1H*rD$kLl( z8LUAJ3=F>+7#N-~FfgbNOCOFHPl3mA6c`x(pQ1eNqvVA-3=IFcAj=kpU0|v}(>^E< z1P(jjjVKTOWoBUb-%aJT4{9HMV_^6f3>hODb^*%Gz`y{Sw*yt2bxbU5!_I>v$3y?+ z85sWGp+Wiw<$<*f41b1W;sEslKz$rg`d`At%nBd)9XaV{xcKzH3j@Rd?=(vPHyIfI zgXVsROJE`UqaHNQ#mK<8kBOOebl(?p$P6j=zd#0t|9@zd{vRi2QVEF%;M(O_|1H)g?o~^;WuZ7NjqKkE)d0Z#fmy{|^R+za<^-eOSToq(P_0TLU{!eCL_&% zKwZHf1LA5rTN(g8g9x;bQ(F1tfVBdL9p5TsMH0 zfco^HyIroHGiVNMn0j;6TO@>l3G^HvPD(hvY85YYZ#(Ak9` z47w#MJpmrT>%o8-`PuFvcD2<#^{d5TdPq)ofZ9T!b^>UANO-_}v+EfH!~AbZ$RrjtDd^w26U%K@TBK2iECdkaU0XKLf+*U#tvg{>U<%{cFi^ z?q2}Ixqql7|3T?r zj^W(jbcXZ))-jy_e~01x|4$6(|Nmw<|NkEpgV-M#&i}i`aPHq4hO>W@8BYI_VOV|( zG_e7?aH#@%_W-EuFK1w20IhqVo!duE9*!XZ+8ff&z`*bxdba;+1_lNz21waTUH|TT z1!?1){4U6F?r$N(`G41->HI&#`TxY=zYOR9U1He#qJ%+640Jv(=zND)3=9nEl-@l+ zU5}2MHpD^z)Q$k1^}Gvu9_Ske1_sa>QifE^2cUL6q%E(?aPHp*hV%db5SivN^TnB8 z{~5Yg{b5kh0Nud>I>!{W-+hP$`KWUTVF(C7?=AwJjR;y~^pb&rVHN`egChgvzAy5f zeEb`zov*-f?%x5j(>-z>apC_zhT~rkFx>tx$xsWb3dncbsLs(4Knnp-n+VjV0NwEg zI(!kd=x!ea0|RL66x2^9{XB)EpV=AC{+&aWw2#OK{}|5woy2hBJ81k9%?qR4(GZ|G z1VD32p!@DXcbJ0CQwG%uptcccJy9J41A{y0K3@g~2Hg39nT3G~G*)xww=Ki@e;=uw z{y}Y{HwoQa89nBm0tRvM&zQ2PJRaP}`~><5R_M}gqd{dq z=nmgC3=9mHq4(o~@&jm02Gm{x)f0~y7#MFeFfyHCSbXvV!$qRTcpzyS(Z(UnKl>MS z4<%JSIcnHw2oM=XQEIQzGhVgDP@dZrNp=fFF&z2G3|>b%)WTrYy`v#;{3~RS%ZYEC3}^q?fcpjK{%vD8|NlC} z`TsAVb;Emx^Z#Bjod0*7;oQHi4CnsVGMxQm$#DED2Y4UA$q{jm(P&y3t|74P0RzLy z?~wJ-SO0_BMeGb`eoHW%`K`%t{=X5!`F}Me;7#5GSKq8 z;TkZb{u*&1u>BEeZ767rlmXnH2d$Mp{|~|kwU9|fZ! zFd71*Aut*OqaiRF0;3@?8UmvsK(!F~!HnIg0}S<8B^ns)v5GS=@M9DI$H2gjP5cjT zacmAc0J^*z)0_{WHa!;gAYlef@ed5lKNzrxH!%OlCjO71{y#Q%f`Sa2zaGf{|NkFL zI5dDl5+mF`)PWuN06jbzSik}H15Nx70|UtT|NsA>hW`%+Xhi*iNW%i@15ErMs`vv2 zsKIFB2XKo+4ETo{zXxFM`A;)(s7F!Fhr1umUW9*9)&F2%fCLGe`~Shx0a`fxZvY4X z|NrRX|C=G^poR1Q{|BMsQ0GE;|NsAg0Ac+=7ykfC4*&oEfKU)cAo2r5AygV90>M8Z z;SV(qA_gM=Ksi=U)&i^0CgOWeQ7MNNP z{evBpT_COk@nQHM!~YLhlK=nz4gA=}AAs^HruiUSv6}NAR19EI{~siQRUEs$|8cnU zKX!lp{||BzBz9nah0_0ExeUsOQw@x8CJN&NR%fCp8Rd+I0CEU`E}v}xOJi+|bKT7R>1grDT(Gyc!c2B(7|?%h%6BZUAc?!RSbG5yWXHu;yG-Ss~^ z`^5k3?C1Zpvp+%c%m3Ng=l^AAPy3gh?esl6hxKn(7Q@qo1SC^NnL{xIKym*oJBRmQ zc6Kc^&j0_<&c=t||I5yv{3k0*98^aPML&10y3SLoQ%oVA##Tz;J?rf#DfuV+hfnf^+ z1H)Mc28JUH3=E+7t%T-x69xtbc?JdsK?Vi}4h9AWW*!b6lnUZsc6L3n`5j%|zwGQq z-?MX=M{X3*-RGdN)?;8`sAgbb*w4VgaGrsIVFd#NLly%A11PQq85r<40GODV7(f`L zOpu+O;cs@f_W$hcd+2dX1pnQ?>};1m**SC%gb`>VsC;)~U|?9tz`$^Yfq`K&0|P?} z0|SFH0|NsKCE@WlJBRUKc6P}B?Cd85;~dmZ0O9OxP#-YwS9Uhbq269ftRI1T|1mHy z{3n3n!2-(fb_@&*D;XFVt}`$&%wk|*u!WZ8a7C0beq`q`JV{Ihl>@f_va>fq$9pi^ ze*dzw|G!R7{(mnv_WHle>(<-{~ins{~Z|^ z{%bKX{O4z2_{zY*0BWK$C^9fGOk`kSxD9Qufy#F#T7=o}%uEK*xbE+)Z0^6=*^d9R zvm5?pXRrRBoxT5GcJ_h)+1cyAXJmA_%F3M-U}rzdz`(%!I}23h(84vt(M(X>gY&&K z1H=D(28RE885sUQWnlRKm4V^^PX>nn-xwJFzhq$ef0TjYPYnZuRT2XO!x;t!h64-? z3?2*&4B!qOEkovYaxw!bKZC~fKxyGyPA>DW>>SQtS=n4aGcs8I7Zri52MzdsW?*1& z193(%F(CkodoXroVEBKEf#Lr@28RFtapQmg85lPIVPJ^c!@wXfL5HZv@WiL=9EKk` zx!`%8pIKQ9zcMo!9>m3g)Pee#j~N&kdO(~}OiT!X+>7@7$GItG9=lqvrVEBKV%=mxD!0;c`?;A{hbAiTx$Y65R;C0J?CI*Iobqoyu z|B{{t{>@=v_y-#M8%*Bwg2un!U~<&pbqlEd|5uQK;oocqhX0`Y9M-nOM}zu^n;01W z$TBd1#tjCC@7TmZ)5#2p3=9mQF@VwfpXg}$%fP_!hlhdTUls$y{|k81!EXkJ|2G*J z{?{@v{1ath0OoMK=A&;NkL1|CC}6oAqvXwA@l1_lPuIPk!`Yk0cA z06NzTTGInc4}-;*pnl&w1_p*I1_lO{HAI8Ofy2lhpgDigyzd4E1_oK0`0Lz128L5V z85qv~VPH7>mx1BT9|ndqzZn>ggXW*nZDf>#mPw%bNN_=gu40r=^$-B9?YjuA4~(g1 zIw+pc{bgV{|DS>3#5ZP!vwwIQ&i)l*IP-^};q)&yhRgpM7(i*`5~%G7%I1t>3=9nG zpz9E*=MJiQXjmJh30>=Ti-Cb5nSp@;w5FRp4;oQGwhO>X|7>>UItrrEY9bM1Bz;K6wfx($Pi$`@)7y_WR zyP)-+cR}m7p?w2TTZ}Yg!Dak!5r%XB8X3<2f5>qD|9?FA1H;je>lpMbL2G?L>&HRu ze$wn4RZZ;>FlAt1SOP7F7cnp}fZAicL|Fo=)6e`CWjOb56~p=e|M0{+QkwYppJDTz zFAN>agBf(ph_Yr>5zRvYwEn_@fq`Kj0|UcV1_p+`3=9mQ{T>$3HU3x}zzCXlU}QM= zx0(33hn5W&|Nm#W@P7}(ncqTK3?3C4xDWu17lGOa`Ox+OsGSHZtLH=acKI_fFo4Pn zRR#t|X@;7a+6?FZ-6SsGL*pJ%Km24k`xi9lJ8-@owHaRsfaV8P85kIX7#J9upliiJ zV@Ri=Ye-Kq7}*_UIQ8>0neqRh;q2dRd=40u8&n|xS|bi>7l77{fyxLYhUfwhhO>Vj zks1Gg7|#72o$DV|QB0y^{{LrUIR9@ZdGY`60>ha z)YnI9<73qI=yJ~)&i?ghuyh?XUKw==nIUlTKWO|%jp5wCWen&4e<4s7{9!o%?>NKR zzn%tE5SX}~;{#9Z)|6iNo>|Zs8 zvwy@GPJCwp$2(}A;23C`7ln3?Y9D+d0GbOp`5hGhkhwq5To7n{7&I3MniD(>TH-PI zoIUEI(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c z4S~@R7!85Z5Eu=C(GVC7fsq>m|3|@S2#kinXb23p5ctE6>Y@jXsQd#AsQdPl$J7AB>XZ-&k$^JiV?En9NK)CM%gZ%&h{{-RI z9{@Y;0}>x(HG&VZ`UhN|Qa;2!gn19ZL4~mI2iU0y{(mt4Bi#P~V5x@){tr;lBJux# z+kxRCoy+hu zE1Thab~eMC^vq%H*TLespV>JKf3ve0{-fY5F#lh6HdyXkdir2-?yz#lZ)lkR&CVA4 zmz^E>FFU*We|Gk)|Jm77{$*zu|I5y{`jef_0g8vwIG}FSe@#zk_?ex}{4YB@_;JQJKw_OrGA5&R3I3Tt@=1_oCK2F4r)28J#M28KBd3=E5)cpf7I<7@^7hMB4Y0!yDHBz*p# zlLHQ4Lh2H#_?fp>T)E|I5xUn(p9$-z6hUR)~RtA)JAMVIBhm!wLolhH?f5 z1{($j24MyU21Z;#!NkY}m-?5T9rQmt`wc9NF=#~IIQTa^TL#odg=-#`jK2&F4F7T9 zT`(W>GB7a2F)%Q!V_;yI!N9=a!N9=450iw`j5wnJtmH>lHp8#%Y^HzN*#ZBvvycDJ z&i;WM{{OPG|36Pm{QEW~W%b|eY}Mf%_9$U4%fRs8n}OkfCIiF&JO+mUkqiv~^%xjt zb3xPhN(Kgo1q=)fwhRmmtl+>TllX&Z|BL?1&i4D4on8MgJA2yy?CgnO($b48BqWaT zu(B3|+N$4&b^8+>{vr$v|LYhS{@-9=`2UlE;s1Xq{>#Ad|0M&%ncWNwTE`g}7@|St zIGI6?q3vg8Cb*9aO5YHi#qd8jhv9!Ns5)T)4MOZ;U|`_J&^T-az+o@M!0>+s1H=D+ z&@hLEJre!69dCPPCB_8)Mf(Zxez+pHvrar28I7@BEudO{`VOe z{>jqGp)3px44WAk7>ej*-vC(i78L$XB!vGN28Ms2wkJK@3Ti}w+O~D{uxw!Lc+J4T z@K2k8;r~q{;@~?2!@o=hhJT=Np@(ysq3v8y-+~_Y4Uiq6I{2SG1H=C#3=IE&V~Gb) z8|Wbe!~Y@%hCdt(4FBlkPDVyhU*j;e%|#zO2gH`&pfZSof#I(h1H=Cq28RE$7#RNV zVqo~ckAdOSCI*JS%M1*?0T8Ew`e+aq%}GW^ZD{)kG`>M|>ju_>AE5Au#=`{=%?@ej z_<`Cwpqt5-GB7ak(AkZ}3=9n085kHqeLp%|Jy6#D0@Vkg@Q2dzAcsgYFfi<3U|?uw zU|;}^pMk_^jb#`Z7)~-UFhtSXq9J0w76Svreg+1H8U_Xi4zk^R`WFMk*}n`7=l(G; zoc{-*LF_Ys7#Kc->LJkR9V0sf1H)wKxZW`C`;r`rpg7pdz`!txfq_ASB;6;!GccU} z!@zLiKPdb;8P5GvVL12C9Ez10&iv+N*zp2nBxp>jf`NenbTy+YNMIDBgn$$@{6X>H zLv(orO7mxa^DvzI7te71{|<)p{~t4)|NjY!A2Xc)x1HheyCenykyZu7#QX)toY3D zKMegF?l8E7=rb^by1po;jB+qT05nJjsvj3KFff3|r7Ia27@VNr+(6_ zor}*e!$;&i`A)aQ^=lhV%cQLGfjV^Zynyocrg& zaQYW3xPLahd_OpRbLKY#!}71w1~~fx0|P%yoB<*Jhxs2|%?Eq9 zIUgAQ|A(1>fcgLbe^B!q*pb*s;tV+04M^e#kn}!~N0{@2AHn|L{{R0Un7K`z|*gxb!&V%s&F&==6|8HPGus}-n{2?-S2H3$u0g5EE{ekUdtbY}Wl;|V?4zSv&^c%z3_52MM59;@ zI{P=6fq|h6dMd{(WT6cjA|fxWc35j{>Q|?@IQ`$;s0aQ^JoA3 zVPH6ZpMhbO2?GP-?hazy`zI?KTJ>LQ zc84LYZWRL;uNU|;}+LoEXX_#^M)-bN{Tcm@|BY&i%87o-2%qchGsB z5WJb;++WN*Gkko{z;OB(Bg46W4h-l2uVOg=|2D(<|4$gs|G&U+{$CfvxxaGY^VNov zf6@H{Iy(+j=bilnI_FQC;oLuEhI4;K7*<|@oU?usbjJa@reVehpK}g9UmkR}8>CKT zU_|NsC0zyRSNfYQt# z!2AaJKi~s180!C@2l0|SFN0|SHbqxkrU|Jm6eQQV)Ca}TsH z2cK=!l>@mOyq;2>f#JVB1H*qk1_ndWI;a@%-K-1@pgYd7?gjW?R>t}-J39!pXXt-U z&fep}Ax9QCJ16|l$z`Oj8}XU;pO=B*e+P7J^fv~E|F0Mr4xeOTNCBNfjZY(d?fakX zZ07%Yc^u44Oy&#>40JAh{<||U{Qrq%O>;J0$3rDSd-Xtj6`*VPLFuZ3fq@aU_Xnzi z*3^H{+T#~!?gzy~7#TK$%AG0(1_nq0ONIfYYx&2_!0_LXf#LrK28RFV7#QwfWnj2n z!ocvK95;aSb}<8~8!pZW>8BlO%qZie&!RKa-P z3vLDm2GH~(Xm1-2h93G0fcya3M*)flP&ouzrw$T>xa7ir3x@Omw=tam{}hamecsCu zlF`n<$S46(Fla~t=zaB|JpmC63=D1zsydDg$G#lFSVMd6-!6vd|G5T@%TXNy+Snt; zz`&rvpkZjiaQy3j4ELY^zm?%EXnzH&#zDse<;bL8JolHK;oLu2Fh2X29lZ7wl($h74^j^3E?n^1$$yY?6tsqN^gixE z8d;;x8cHDmSzZKcO@dg%ivCdlAMBq4_F(!41B3=Gy8`WMWPSkVf8hTCq2>Q!rT@tP zgP6mA075r5Kz4yXV1Up+p!EMn2><_KDE*%p8njLegag1EAVJ%rpu6TlYqUUkC?%#* zw~mIuXb6mkz-S1JhQMeDjE2By2#kinXb6mkzz`1s(0mVQjv0o3XA?D5L>FI!+y**J z>R)y?_rL6H;lI%H6o2OA(#3v4mi(VKjp=`OcKHA7?0x^UvoHP6&R!0hZxvZ|gpgJW zv$} zFaraF9RmXcXcz!AnXSvfzyLZMM6}J)Qu$9-*3SRg+2FGS|72zT_@A9!{U|(%4zw;q`hRwI%Ac&Pnig~ONl|KQ zvVXI|XXFuOBWa5MJ3!~2!J|414Da7FFg$j_WyYWE9QeHR{~WNJK=W-hJa6cK3~1B| z3IAbW_zyZ05tkFe;%N@949u+TAT}snx`G(A!T$sq82&dgF#JEo!0_${1H-+^3=IGI zNw5oazKbOZ>Pb`z${RZwAZ|(ntw8~ur2@(ysOEsqRRGNifaW$X{AXa;_X;Ernhyh= zqX|A-2_!%p3@clza;r#yqhV%bZ8TP+%VBi%9VPIeY z%@>38(G!E_;(Qnw82lI*7(i$9NHb(N$upe$yPDzr|DWKwxl=zsGb}pZ%@CE3@B=+v z2mzpY1G&$EAs{J=;q))$bCdovT>QVB;p%@HmCFzZ5KX@R&%$v2e-^{}|K}Ob|9`-6 z{{K$!c|M@IDxwXcp+ZpJxbYt}UnatE{+~Ppc>exB1H)iCa|LuZ8pxfXbrB%?^lusl z$f(gnIRrqnQ=pn*2+{Ho_x|CB&<*Th`X2*?K41qCKky$^gE9O^ryu<92dn>M0a5pl z0cswM{s86w0ZmIW5DFL29201E3A8u{ejWg5wFzjB3517o;uv-NXb6mkz-S1JhQMeD zjE2CV34w3f*$h9jvl)J-)BmhP&{!>KToi^ue0l{8XyNhyygbqW+1Z)@v$GrjXJA&o3(3m1vF=YhEUho(p zXe<@9?*TMM z82-;>VEDg>f#Jd&1_me49TX^Q&>0yR8U9ySGyl)d*1Q@S>0&A-2HFn<3L~%ylo9_c zkjD1@GBErHjfY_v2}*C^v<%^UuN*a zILN7EpgSHw*#e=CY!+W`KPQ#p{J$UIF|~95{xh8Z)kcb86sl!oSad>>;r#z) z4CnvfXE^`=7{mGhpu9<;u_S1pcNjG0#KCa>zZ}E4e}W7bLFt7A{gf&_^#f!-XzUe2 zgYp8UW{x2J;Bf@15C{MN|H1hGe*@$H|I7#f|7ZXC|3CZ3|Nq-B{{R1g`Tu{=p^AS% z2P=XPNrc-78m9nl$^lK#!N(~;vreEf3J@MaaWU%E(GVC7f#DMZ@HQdz+y!d;47B0k ze|EOQ|LknNf7#ib-^f}60;;b;7&He9b|I*34Z5R6ae<4A+rO;r8{l&ba&k&YHy_+~ z1MQntV_?{uv`gl zmx8J=GS(P?!sjm|1H(HJ1_lGr96RWeB}l&o)Q`CEUy@ONl7-sAN^_QsBUI*_j7iCy;IgsJ}|I1)}nfh%~ zuw&2t7h^d8PmJNrA21i>NvdJc{3NJf1M1&^*iKcL} z0IIh@7_`=qROid`@-qDI=wSSpl?57)A)p;p@4zqvs9!ChE+{DRB|W|Ve@@Qa|2a9J zF)lo2{AXlf_;1C)@FIbM!3DHlO-w~lQ1DwuM$(;_mHF4Cnq{VmR^ + + + + + + + + + + + + + + From 036f412d0d0863d2927d96c50ff19e00c5107a97 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 13:18:10 -0400 Subject: [PATCH 0618/2524] xo-flatstring: icon tidy --- img/favicon.ico | Bin 303879 -> 309803 bytes img/xo-icon.svg | 31 ++++++++++++++++++------------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/img/favicon.ico b/img/favicon.ico index f4e9d76070462c14b4ebab0154ce19516d899be9..15da2145f93eb26e6995c7868ab7883fb2361288 100644 GIT binary patch literal 309803 zcmZQzU}WH800Bk@1%|zv3=GQ{7#I#50EsIwXaq4aBx^A+G&Df@9E=RzHB1Z%2@w8@ zDGUsoTbLOf93XrRCkBRSNfrhJ0|#lWE5$ikqY0O79?U|^U$ zn}tC_0>ba$WMHsf!@?jS0O4n_Ffbh6%EHhY;OEZEB?WRQucwDg5J(n;IoKE&7-EW6 zq%kmPws^WYhE&{od$+tM=IYE3AMfuhPcxaMb8?f;X*TKP$89fXBpnJ8vRbI#B6o_- ziF3Bot52JDKjHOOI6Y~T;X;+!0$vIem=+3GZDn3IE3F`ZQfjI1?(g&dKJGbi!+6`N zGjlZ0fB!sFK*jL$otf41EdMqLIB_VpkW6qn-{f=XSa@OegN62s`>Yx+?3Nd*tCv)? zcj(PObi3H>UYp*YTt4L)H=5#ho-5mO)L*DUBWnL%p?k4SR?jP5ZO=@zUOMBU)tLer zHTOeXSQvXX`#Y}fSSWCEg7^OC8xLMz!^I?4E%!eEMcT(trfaM}%=gSOmkaX<`XS=U zuxQ1f@2MZwz7EiqUs(7i;~7KyF23K^e3z#t@dyM(@60a!RljAKJx9cA_B{(9b}Bqr zDf+si{+XQ0xjh_t^%uTfy`|3SAksRuzu}sNm*LiZ(rVxN`0MS2Ch9vir>{Bw79 zl(7G@ik0@~dUIuM7MR)^Z(o1LMS;^HqVUwig5Os^+zWcOUHZJ_MXi5FOJ`2~E*rfa%hDf78m zVJE+(wBTfmb7juM*bUEDZc{!b@5!L_{)-dCs^*5%Qy*D-32v0VEcTeyroZ2;q_FNZ zpUuWMvN!%^+zx!&Zo=pix3`Gh<`2_CS+$S7t2m}>yH0&OW1AaepL25Ql)crrD|e=f zW#5b7c(1orHnMJAXvdz4KW-LJm|j#*3|_tZ$JECc60Dak>{73&%C%0vdVSNPmkGBo zKAES&Fi~9pzudQ*l8-pc_}hOjer=HZ*ziZtLenKSc`;W{nZI@yl&;?Jwxh`V8Dqzu z88(d5)+{<$_@n%KqEtXptGt6IN4rJ!9SOhOS&Xu|BRONJda%Zo7<22_5J0}_+ zQnbsro;NY5E5qc__S<3xx!YfETYOU2lR@b@-(2pw=Z$oujxO~Q+$QU8 z@6(0h_G#9$Wa6g3WpJsC{K;V=(;yXk)^goxo!cLubOi|%S?1nS`6dy|UbXLSN0GBo z!-gqmDTQ zGf$RnH=3=r*>dTD(wpCZ&3|lHx%q8|+p;Dzd!e5p@%**5KIJ04+mba|o^E)&k=-yV z`&RC8BcVwQE_p5rlTy?~i~L(-kNMAYC_bV4mPNsr!7zqv^L!T$j+L_6cPg4rKE3zv zzDM(W<9lTS%AtZcqj+X=Ut##_AZ(!bH-qgM!{Ww32a#fa%de&@Q#Jc#bJ*iFqJpp9 zJ-*}FqId^GhOPSqgKsG3EIcMrwC8%mfp14ex`K8rek~FE z%2dviVXAB5zOCv^){MKEs}s2XRh%*Zw_#SR*F>G^Q|p^|Cvdc`(ERHARfTbiy?Aix zebyi93GzM<<@tANZ+N&tn!A0EkUn4Y^nZ`aKjgX_8BJn1HM#rewEO%PEHkijYP$4qT04inDsUkol23k-6<)id?6@UbkLF#mi+@~z)>zvkx|9o_XR}7mLF;Mt%Oc>u1sRaM{#ctyJrti4$1@qGWuc|8>u3+HdUqpQm<# zZ19Qa%3eIe4fm`HBI*Lk2d+913rwu1R|K9+A0TRb1=ot zF4OsY{!WthERb{Oh%DPMpLb64&tvjE3=xbrjDJ{XynnesRDb4o$*OLB(?(xuAvQ52z!~v;4BtHY@q;RX+c%hnt^}!$%HgcE~)=@&%8!CtO|p;J1zGy3XgT56NU%FPjr6kn!iA#Qyv{ z6M2`<$+TW@N8@8AkE&+R%x%^D1$NPTe>Q%(Ker)MX^-1Pv44!`_i;^LcHyf0<7Em8 zY1Z>%KG{6mUiRU6D))nT?m6z0r6)0PW%CrUF5}w&TkN0SfiJJ-cT2L>@EMD4e=+x0 zZNyHITe~Yx@K*#(|CRF7WNqhCn>xeV@~HdqS=O`eO!PZ&=qBe6{|BEp9L-gUYLsq_ zPrUU)B87rh(vlWo7Z#cY$!aGtKTR`F%w^GQ?Z#EUM>e`UB$`IPKR#wYWcWti`@ zuKbm*{l_lN<9+vmsZ$CbvRC{`mH2M??6^?Odeb;hv416RRU67DX|9{K$G1|nZUW1M z{J;l756ovsIhd_Ius_*~W&Y9=w|_Pl-Z4*jdicdx**lNe?*te5HkS0NKE3=~-uy$_ z-+x&pml*}Prk>n*Aam)A>E5o&1(pSdDYAT*XK>ntGx!IyvHlMh-!8DL%29LKwXovZ zuQ$wpJA+N!BJG6!f&W+U>ioS{+Vj;my?3>R{;E6T)lHLL^ht_%GW;|Y+`+cFNI!b2 zecMZ(JzNo))(>hT{~4I{G)*~v@9Wk-dNN7ww+(YIIy}7oV)nlkzxGw{<@Gnw%$L*4r$(;;uI#bN$^kh1TysuKYZ9ptphPVc7?Lg>dP@DIMX^Fo|=Pf$0TCdpGjj?re?u z`+@t*mm@!#+v<|IbC%$us|xzI^Y! z76=Hn?-Eu@+$Udjb8W)b#$Gj5)>#n(1^MCn;pVbO7S-S3tFAXXZ_g9M_J@7ud7F&G zXRg#-coc1^G>O42&Hq9D$$gX7OaD;(VDE6KY{SvCpUwuE@e!YxCLiy8*z`&xUGwM? zr#n*`kA1d@^IP(x@;2K)v5a5KeVMi%Ppgk&j^T~*diLoPTiTZ8D%oX|7~1mH&#=tT z$^FOYBl=0qYtP&n`K#qx`||Fr6ux<})Z%!d#l_Maa;=k3ONY$<-}#e~yXyDOt!tZq zFOzq?oxi@d_c2F1=LHH&P4_w;_~o!! z<-(qMKA)1}=Ul%RcV-py^9P}Cjo)nGJFN2cY@5O1Q;|=F=PY_qxxeJpew7AADb|?w zmyG9@2!B5NUwX-h=RUuKKU|t^VZ43$I;}G|p1DN{dCpp;WYQM1sIPgk>B@C+RfiR= z{xn?c%Zr-6Hu13If1jq-jIGZ*4l@MD?c9qg0b01!8 zeyK4jcG{`i8fW5qX1-(O5B@Ej`r`DAxW7K~HB3M1d;SFY1xlqQeoHx1qOYU;yG9^z z5#N!T4KwWu2JwgEa$we`jMH&V%|XQJ0cHMjp{E*?Pxxj`&9Rox5(w| zlV8qz!MZQO+QG1>bjH0uIj#Ra+sfkRYv@}uzMuT_UWHi3HrWLdcC9nnX05&aJ~pB6 ztwL+*^rUN5DX#?n%zFFa$cI|K&3X305A)T3uQ3RWoYk;@@=v=;*_DdZmgE{n7pDIT z2tL3+Vd<4e!p9Ta8qN2HGd#?q2llLLxP7vQ z-?r_GtmxXbGkWW1+U+oVAaI+pYf>T0Om~*5#>2Vn>PElK(){X|u@s0JJyB;{&$n5} z;_}`S~x6l?mJ}#i=JGaSex?F==QsP*A-FfHa zH6D(J&$;aEs&$)Q8pv|Lnf>s&VS-$m)dqo`7n*0f?bB?HG*_71#I=TVb9nT!{QvtC zjkXEw{9<{=Ba-4+8e8wlo(AIdi!3kUC+9TDS&Gd*RL%zYqv6fK7WMe!s|2J|Gi9+tFGm{{9E5; zpU#mSSJOf+&FIkBVtaB=@`u$k`VO30VA#4}RPE=7 z)d#-Z5#RD4-Gil54ML`*P9u#C-mFdI_cV8#)sf zvbWptWi`Jw_ntC4>rBDtWtng{FsC9$2rk>2v9Gc`$od zkXlYhO{e9|?{il?n=HDDb?Rr94+;Ux7b>2dc}i!mZsjSPG)8fS&&)?YEM?j(dSlrt zjp-~K>@U1ERAqUt6S>#Ud_C#1z^Tr#?*Q z@}AxFM2FF((elEYg#SkC{xL5|%$MIb`67cMPu#Tm(UxjD>6`~l`)6`3yRl;e)9eQa z);*o~!X++cm4%kh+=~-0i(lPfEn2W|p3&8M$IrUsQ&~u>av?tRIjZbrPVp>!; zo3^Mjb|{_-yZSwsW%6k$hmeCI(&CBEzZxcOTK~3V&gl}frE0~`6Yf1-!?xgT$gx5* zy+drgXKf8`mB}o6lJe@U{#%v__uj7$3lC_#5a8RgtK#X~7kX~XR%9_)-fUHw>$>RF zr4%-4k&vsZr*nkaC+6$TZz|`=TJ`7u=5iGYS$ibuJ4{_p-U@+T+r3#`^z~ z6MNhg-WOftT%pUC8nLJ}HFmv(<@7h42fTO{)O{NQ)AE9ayqzVdRX7|f6IisQ<>a1s z^$#L`H=TZJkZ|UjYs91rGt0{bm1avB@yi}JP-hX1Ik!#e+J)t3`j$^!q`vM&*=2G4 zBMMh0?c}`ByHx-D994!?qZ?8nUte1Iw%4$PohxIrY_Eo3X^J01Y)YKkoN`M~22TYh zPhNS4pKqC`d8#}(yfXGn*y%|Qh84*t_Jr&c_WtW>`I1>e;!pp<*&%+dKYFK${M+UA z>YIr2Ri|ex8>YQE-SSy0eQtxDfFZlgl)r&hw@Pm9Sv9ft(9sD!>cy;;x=Z&+6+GyS ziQw6j{n&{s$5Te^q{IS=MY)^)+=;p$>Gz#EV43-6&uH;r4X$~s+qOR4^M2-uodO1$ zhtCHe$clD+x`l1Ry2Cb0PFGk2INLO^GjnJy+#LMG$22Yar|;KbK2C-+o$R(BtUffY zTSm?OR?osp4X^f z(pw;9#_)N|Qc=I33l91p&=s}`+Y)xlhkc^A^@PetciSUoF*8(pFg!M#+sGxy@J@gF zrbQ25#_cF(el}Soi9_UB$x9!x+dMk@)f259_Ak=syPg%1_L-^J@o?3H<7Y4IyHsYd zNaQNhW#d14~e_b{FQbzdR|hM^wiCVJf9q0niKcKv_SajzKXEVAGbZc%v^D+?BTsz ziXHC$7vnhTI({J&uQ)u#ER}seGFPdGvK=upkg1i3p9;+Ml z6+9hy&SyMOHPio7J7bw+Du==3mY@yI{PnyF8)6@r>;8E@Bmb|@%qrf3C{v56Ti)+H z(HYxNnp5Ymd(4M#mCX5N>uvZRGyT|UcDZe>cpYz*P=*>IorLb4*`5t|>M*{1K5n$xMaCDw4Yo$O z81PGE|F#uz%{4!hwcq#s{L}*`w-1W;_THPhi=mJA!8wL&Pj)RfXei@<_xZsUKZ(VS z>6_Y3Whmc@HUCdOlOqy5C6&N3By7|v|{d#`iRW2XJ;pKXg<-#%ay zd3$5qu`@~~3wCjB3tbkquiGqidFKI%RIx~xEjuTOEcb6<93GKd@_(Cxhz(sT~rNIixoK>1F>T7<}~ig|Y)l#rJ<6YroPmeTQ@syAN~U zH|FmV3Fb@ZxCPAD;(s~oY1mR>XQ%AFB74-C_kZ`_e&6cvlYbS*f6kVdd3q-z*7nPEl-~rX?EHXtu>NUz1WVZ=d4KH)<1K zxV&cYiNAO?;toqm@Fa$|bjxMJ=}c*>m;2Oqn;vjH;5Xy)q-!_%c3p}o>OA)O*Q&iP ze{`;_hZ%zsnR-H$Klna~H;`?;S<{kfcBk>EY=ZXDT>IVAo{P`zdi`TEv*|;wjyVfO zE;`+N7gc|0M*vriedqhH`@4(wUBC6~7qhA9<3HB#W|(dgKgsCHpe)YjcjV05dHY)T zdndGUu0LkN-L*(~#hsXz?cXRS^39 zb^UC&e_NCld>bZmH*7bY%(eaKncx#wR5%i+waGQU2kA+vTZSGE58&ENTjK8ydk5#=(8p(tJU1IMz%`QkqE z4r;vz7S5=Aq;2^85NmTKt)>k_wZdiUM? zk#0J1_LL$u@ywh5XIwiK==FAGsm3Mw*HwAz_v(F*tNl8w>9``N!grHp4)#qilV|dV zr2f1el~C`~d*FD9*r^E*1!OCKlvb>q7rWq>T0y#4^~3f?KBuP{b1WAIELp9#DdnHt z?>_|#6rT3-5v z;s0hXd4``eZcVm-`Rss9ytz)$!e8IF%i7N^zyCY$ZyC&n_-htCttG@_$|9`}U;%(h%MiH|thC{@8Y`*1Bn( zcI4%_8P}(=1W4RH5PYEK%*W2jC;#1ZH#pn&*iifAEd$w}NgQTHKg*{J`)_@)gSXw` zht%3Rw}N#37;Jdv&>P;hsw3*hi*J|f9_%;y|D3fl*OQ@Dn8CYYf8xHIyo!dUt!T6st4Hb?9}{EMF#WvQNxH_r3hi^RsP? z|IgjCf166fp>F2?406Xro@LHBcOWOL;jSV3ls8Q?J$Hz0{_p7e>)%p)#bXaMWbJ}i zo>=O&a*s?=w1-z?o0j{|WYL57lWH{gB^>1c{BPGJhBr4E{>U0Pt`NI1Pxrl9OmAdE z3DbuBU+J$6elH7|V*72Uc>BJ>Q!D>+THK!cY5V_UQ-T&;aVhyww!kT0*2ewgA^Ul! zUT06Md8Sh+mnQw`bHinWtxaornfsXfnEr`Z2>g)Spe$Mx*BHm=xt`B{g{4<^y5CNb zfBozIS8GlAoEpA#VGEF9TgG<3KT`cdOJvZVrA}{`{eLFlvo_)Rm(N>* zj}>!T?O{nY4_(|D!|CVQ^OL_dXo0Ejfz7jasd9_``M+o1yekY{-sT17X_qH{Ht=m( zE1K4Nv;LjTnLHQ%Bs1lk-EW-xR8RApL@h5nD)T64S;*XL7au=6%e~rLuf}@+`jp>6 zN5xDN(m3aJ-;7SsXrH(xxlLC7z?{MjGlbSl&Z+x#d&`zHYBRsAWBA_wwB~o#+x?yI zEM+3B-?tqTw2wCnV%Eruow1qSk8vNzonsHoC+8gcBKn7^f@d@D)$5J6niqq64?V29 zb$j}CHb(ZTyQ|1xv-%DX43 z$;-CSOx?k_IpSsJgUT~v>1(GP_|ue8qT@K>mY-(&wzm zarcy<#M>XAL<^|=yLJEbo9Qy!?SAgxf4lsN|BS{f3|_$vkp?Bg1=a@a#>pQz&a63j zP^?et%$CPD4@@`o?dxx7ozd%m-|v65^fYt*nUY~ z)_q-Y{rT%s6~-z1?9QYtdfxE5f!W|Uk4(qS{)7$Ad$}ZfUn<`a{*iuW&0*66)iY8) z@RgfXbOK4-XqW zem?o++m~AymziDQUBiA$*>8oeQ16<)qrap7HkkQ5PZqz>?5V_g#_+Y~85TAE%a=tH z-g&d`SU5qtYzm)huw%-Z8FOd#Tff^lJ1blLP1bK;U6!?5+2`=Q(*OMI>GBm`ybJy; zHMoC4K2c4w_xKs9KRjvXi{>_@xtVESIk+Z$#nGG^C#?@2-|Dmch3ETo)d)>yF!N)w zTlRrpg@L`GHom>Kgrg(x#)cJTPfzVPKQpUTZI{uv5I)CMVJlCZn6LSpyMyZy^Xg0e z?uK(4ZYNKZec$`iXHMfxXM;u6%FaJa47c_vUbVTT{;KBBn!S6qb@!IGE;T&dn0UQ7 z+0&Cj>AT4?=8tc`JofXMU2lEn(V|$x&P26m(X}G328B9u`d!YYpYs+RlFw+WDXa8g z5@0r2R%U?=Oo_Eibe~~P~(iz8kbbOAfbew!ypU-xTp>)Qpx)vF=j(hv~p8oc9 zTG+Ltbn=4Tc6WJBt?jB1clA>>wEZP*P|U<;G2`v|Y{Sh$#}l&nng8b;(#gy6_`k2s zU&uwGSU>NM;7UVfW>4{{K?2-UZX4&_nPa=jk@Wk5A6$>UG<1n)c_)zIFRnO|(xGo5bL9GN6i?Z*R9^M4I)qn#;2s zCeFFPP3+jUKbHCHl{p=z^z3?NcFd)2k6gvhIq6KZcpbl3xmT{=;n(vz@5l5#d#9-| z1RB+S+H6_!QkrS)-{TI`f9~Ws_$MRfZ2QCuv-a%kQeg-TlDXQu_|bW7(LM3&wlcKx ziP&7YYHw_)HS2!&6cz<9uLtEqnVAu--`<6LO@F#bq4#^^rQCkEJEk!|A7@t9>UuIP zT5{(1)s#KkL>>lw(!20;t$g%?t!f;9s_TB8<~+U2dj5&uuRWOrRMqeO=bHPrd|#*E z-RQcl4D~DDAAh&w$057Cy^qiA**{B#A+Sj4YVk_h+P?R@{`p3|PJOz`al$nzkH6oZ z%|5nLbJ!w-`-rWdmglOQTxI<59jnIgjQYZ4_i_<&1=uc)vE$@empD{7kTq_DVu`V zwa5MQJEupTzWctf_FCbj@T|4JbB>#Ycx4`_S9ntvFW!01?{2~4Gfn&UsW1dy%G*&W zr0(;2$@=w=!#=F{f4?MJ=*)}O?Fv%w{`Dc>#(kUEwlZp z)>9Y#FfY;C&tchrr@cRZuG=kJY|A#iN9U}|wJLZfF}U2cc)ur%$^KqI?A;&E$Jg%V z==%D{!B>D~efHbfCl(LI8K0fqJbC>RPK8s)^6QNIcKz~jXzxJCWb(25cy;o^&a`%a$>imud#IA z)Bn$|M=f5!|Mc$r-<|VW=Bn=zTeVkV#`o93Ir~lJC$lJ=y0`QBx<$*k`{r8Lo%*sw z{8v}d#Neq#>dzMIEiheRlJV8!=E{XcK@#{SB?Yg{1Zcgd=Tsill<5%bQ36|6}Ks*8daY`y07^6HBRp7tN!bNyMjoY{2U^S$Qh zAKets-(34s(vwL*^!bs+{>gVYSzmp}z9!?+s!Vwf`DYs6Skq^@^)kjx_2Woer6>7s zzvfS`i|hxrTtBd>%y;>29~yl1t7CpmPS!o%-fjA&t3i$_`}Okugilkl@~-fnnrjgG z`T|cuTE?NtQ;XCzmcH8J=eFfq-{T#c97nfYd%g8rxJFX9tooOItF7#LN`CJ;$Gdw; z-rmI$ZqjV)Tbx7}8SMLUR6Jtmr}yX9?T!;$wf5T`m;0v|M%WY_Gj-h?mtVhsadP3S zKI=5rKH-aw1}irEwvfE65u_vV<3E1YZ>IfMvd(mX7Lr+;?+SjaXuU}bNIe)XVI{swmO#haZHTOq$ z&a8jg*?oVqvdX??Wrcpt&M|zElP`F^sGRX?QN{2Mpi$oq%MkdOlEQE|w~*~)W|qC@5XP^F+oipiMc8=$ltSrI*|Nk=_ z2@W2XVKwTh;S&NMvvV0<<`r^$%FOimm7TrfZ+7-;Txp#c@qgLb-~MH1AN`e;mGLPf zL-cEIKEt1^tl<-2qdplHA@I1Qg5h{$H{-YLY@r z!)~XfaeU0m9v0y?>WQHr0^hT<8D8ZTa{tQC$^Dm|{fLA#jk6tu%Zz{7*Fz$q)ebZNFvbFnr0$mH3^VGy6Y*wmq&iO^*1#?Cj&eGBYjz zcXTk^i;W$U;Wz5&p&J6v5)&ETWn?nENlTaiot?eue|Gjia?&v+dj4f)-TsxC>HfdI zp5bF^>d+0$QI`*?5HJ=IVfdPv#qc>ROZscZ=Fr|9`WoO_2o|WbNKQfSETl>@@ z6^^5h9hxEVF(;SdU2Z=A-|XzAl%`*5=>MCYbM0GBuHpZj9EK}3EkiR(T3v;R;d{Zhk>KiSzk-)ClveHtAX8WQPfC_2(7Fq+|ib{50;tSq;G z+1YQXk#4DI#=jrg*)5y03YqR^=MBZ+9d+{{4}njaSq$&9a)f?oW$&Y6x+QrG6qnuq zva?@)%gVL|jZ5_dSOl+|7B-?{F0gJ{68~;f#Jyi!5yfht{V~| z&}L!50BTeG%+3k_m!16+U-}yia=){)XWuI-XZn<1G$g`z)RBWb1YV@3G2BZ^VfmAl zy=*X~Z$fVPmz{m@OK!f>x7^Ww@Ze5aL&J67va%VzW@jsc(iNe!H%R3F{L0RW0?h{u zjSwDnJL4&YJO(JKU)7#JAX85kJ&7#J8t85kJk7#JAT7#J9I z85kH$7#J8V85kIB7#J9Aq1cjvfzg(U|{$Hr9u4nj0{Y#85kI!RvH_BgQcyZK>y3me*Y^w+vIol z=seh9NSHKm0|&GW2bI^L$(~*Y28Im`3=HQO7#N;H)9@b#1_sa|IK{Zq)b!u~oE)mn z)zTpkegBu8?E)INr9s$_8cE*}V1%|?bQl;Iq8S(%x)~T4c0$W=P?`OYTIn2@Ig{<} z|Nqa;9Wv?vUv~DdZ+ZD%|FTEh|MX3&RI?4#R##zQUcB?|-B1AF3rt zIvB*mz`$S(ZL@D-U|@K}z`*beSDB1Q{4cZ}|D1t=;R+)o^FcON_En5bOjAJDoii{n zfF_3G85kIX7#J8l7#J8F7#J9=7#JAL85kH$85kJMKzwFq7Ld4yJTGs;{kXXML#_Su zH!I6|H2u>d)ly~!7c|XPGcYjhXJBA>%fP?@Dw~OIvwvq`V0Z%U*Di*p?-&LK20I1@ z26fO_4-+FZm#UZ)_<*&UB_$-e_HJ@2!~K+0u0L5>gL2I$u71S7?Cf{nGBb^Sj-Hi5 zQhd`%F(@s9#vVa~6QFiHD9sa--a+O1I|c@ZvkVLjptN4hz`)=N9q$2+Yl6l&sp^Hx zk&z5*JUp0wXJ#(JmDUEM_`mGzyWg^N6zU3 z(=)MoYgCMWApjb?k!E0ENQbs>Kz&MlX&%(}dCkDUu$O^>0n{e4XJB9uV_;yQOM1r^ zETA#OpBWiG^gQ1LSGp!v{7-iF#Q#l|4Cmt#v3Y4!jP@Y_O0S?cP8|aS!&L&~Hn23m zje&t7pMinFfPsO5oAyp5&W11P84RD()8+qVXWu3^{SC0XU%zs4LjRAp{fUcU>Z$^z zdo2bAh7JY>hWiBivY@))7y|=C6$1l<5d#AQCv}}d9n)SGl`>o|t6~0=le1ue(=}1^ z4}Y_>Z+_0qlKYl9dQK>HQUFn=fZ92r^1dC~w#U`R0hRTSq3wB4UqFB;(+615pX_Xg z-`P2!HK5-Idmi|mojoNnEP?T2>S)`aPRRz;z6Z6{@8Bu#LF4%685kHU85kHe7#J8p zV?cCr9_7}2%*bH)kdYzyH#_^#U{3#_v(ta%@9<_4glS+@ii;U6x3E* zn^#WAJEQXCg@7yr149=B0|RJ`5?i_lwR1q_eFSvP8hN%1LR~ICv=3_kTwq{e0L@W>=BH@j zgn>8mRaOqejO;STpV>J@gQ*?#H#>X%hm1@?&>39=A4erO1Q`m8n3^_ZYC9vOq$KNlGo7$OJjoE6j$G^36*^)md< z$YA)IlPmEzD+{zXj_zkc|I5z4_#-FR`hQL~!>e4-v^mYZJJJjWjnA)TU|{%-WsN;( zj|k|Z1%;6oDA@e_JtKqRXGVtL@9gZJf7#hzXkPY%_F(4Z{QZ-awd-3(hTi|wWQOMj zq^;@3=8sV^a0r0bE`a7_LF+A%%X=96G6Mrc2m=EHs4ooGFiJ2m+{(^lxR8;<{v#_p z7PR(&>UjW^=5urZf6vJHe=0Qe|2$W>!rO!u1Fr;dMmYR&l0u@34wBHc49v`&F z5SHFy^iSyd37~Tos9@FbGh$hKJ_AF@OolJnIqE;MvZwx~;{5>65)%H;c69vjqoD9# zgoEQhBO@c|{0hn87m5SsBSGk%@^4t$J1-a*7z!8|7(jay2h4#(#p2r;*$k(W(^)@c zWn2Er&YAu%JNv=^?Cif}*9kc}Z~JX+uQD+*g7zxI%A+5Q3=DolB}}N}PO#S*85sUE zGcf#TWnlQv!ocvK330p!*|vbjESKR}pA0%n1hl^j)Gi|1f|024M_xX|kNg7W@40!Z zKeMvZe`RN{`kS3|19~quuKe)tUv~EQe_2^ier0Cv1)n*Wn`>?)A#2LS$O<~s1)K;# zWfUU=L;vs%SI}`x&lnjP{_ry}{5ND^_#ef<@V}mc;s0y~hX1P>82)c&VEDh0f#Lr$ z28MqV7#M#RGBE!1WMKHE#K7=}je!AHj1vqY(Eiw6pzuV)pt5E?^n5gehKwq}9RmML zOBp_-XD~d;D`b6{n=kb(E8FUKR#w#a?407i+1btiv$MPYWoNg2$;_<$k(rhJB|Y8s zb6T3}?YKCu0AouAUUm)!7G@R>1_nmZS`&EsXJBAB$-uw>s<&}lKBQzoUIQ&1_{+t> z@YkAw;cq(w!~f$94FBISF#P|+!0`V+0sMo3;r{~$hW{HF82+a-F#J$rVE7HKAFu`l zXubUjv~&-01ZY2bAM_kGtQtoJ2P6bQ3wl8D1H+&~(qcedLRCv)-uchP!0_LXf#Lrq z28RD12&8pFdEqYu!@s)>41c>B82)H8F#KYK93ujEIOwcP(D*&va%esP?JX~4U|;~9 ztp-;-$`}+O06Lo#H1>=fXSsvIWn?=7>=jU(-VEBV`AT8B$CfW1FfjhEVqp3t#=w*Y zaw+IskZaKN4t4;P{gi=$5j1%Yn)3yz8^wbw1VCr@g4S0d#~EnueTc>v*c;po4FB^P z82&$@LApoI3!wJX9tMWQ302+f>04=M) ziHCuK0W?noIvW72U}z8^r-Pc@@w`2Ul}X&;&oe*R}*h{racdyRpC9kiBz zXawKLb|q*-BPapE@COD4h9T4kfI0iWECa*;^$ZOE|IsJy!}`v*|1&VS!W{uEKR|tf zk)5iBj_<9}*5HEHVWkWm=Yx{oUkT_K4YXW_rA>O#_y036c>QN!6nezKC~V2V2s&bR z=!D=1cPVJj_c~e|2y`~^5C|4fR{g`n!0>M_ean1o`Reh128PRT85sWsfalM|K-+hR zKoE{P5wz}VBU<_gof8H+Lv`?m4)Zex2A;Ky3=IED7#RNl8u;`N%M*XEGcf+pWncvL zM+R^3jk*ja1lrKjKWOX%v?mos(ct0yXJB9i&1?O(VPN?8Z1ALgSRVMlihIyU=%Ffjax z2e*U3TiORUv_@?w7y_X29?%^fptKLdpacfW69hHVUjgWfHINhkgflSw|2_!IerSI9 zdy;|qtvCY{=qe%lhtjBxB!vKIFE*&n14>vh+(42EbW;q_|n!A5CkbVGV z5j3is<{LNfWssdM30gw`Ta~DZb)A117#M$o%F|CnC;k85%E0i49dgAe)dOVIK zlR-`bnU3}ounL^^LHpEK4yp7H zUYqe3v6li?vkG)i<{oId56c4! z85kG_$>vp1clOO$lf#md0Iuo*4=uO2*i3WBpF=v;@z zLoEIOU|{&|F`EAAn3Bn}3Uubf8npBe+7|=L7vz~pRbAlp4{~115KI4{v$pg{(?3-c z11$^#tpQpNN?=GBbUqwte;6(7Bg0H^9sso^{tlr$aEXE8zu0K{CnLJ)s|D1~2d()8 zCo*Ur*g3e)Edx6ZRF=LPLg{}w1H)ft1_sc&J^DuIsBI*Lzz|6P;65-f1H-?ALn!@c zLdt&7F=Hgf$EafZh5)Gj13LQ+T*yMp|LqJ644?zd=<5=kwt0^?z4E(>*AWfiP%1Xb`pkK>-D7NBjYuU;bhUt}rn$yg_mGC}&WI0Jindp!;D4r=wZ!L#wLaKG6G& zVC#(sn%>U9@E^2Cd2oi?sH<>=fHngI!z*Z_0u`_zJc#zcU~@L;e8JD4`?v}i82*17 z1ogm028MsSqwOASaW&}0K=(C#K}!Fi12%mJy{j2O+4MhXUrZwd!++3ybpuuY-(+C; zXA2qc0R`jW54lknqJ%&=0|Nu{8K9u^5Y16k3|bB-5B%X|VE7L@YvjuS)B~V3KDMLf z{-8}D#5k-Jsq6i)q|A# zqy62%krv2u#dNgve|G5I1q>R#nhLt(+M0pk|853`|G#Nh7yNs}!0@}Bf#H`JP$62!Q6kKx;igkqE<}yUGX2+J9042;_XwIdOl5q4z*uXJGhG&Gpis85sU; zU|{;{$iVQ41#&MuDefCpI|xHSih+UQ23q;wJqX=Nf|Ec;8GU4AVEm=P!1x<L>O0lj=^XJiB| z860LoG%~#dy6+L3IG}Byn+yyL(jzk{u=-k*fq~&9G;Cnu1iCLo4Xf%=!J!lapmTrF z@4f)7gC1n-{s%lr^U&r;K>1*h-jOrlVKtN-pv%C(fPU_0?NIUno!kn#5AzaQ9dMh0 zfnl@`pi>0U$eJXyw*NN<1_pN;*)`OS1)YZqT6hX7`(e0csC#MzyC1Z-3v|XCEG|Jb zXdHC3?T1A`;|oG@w=6+!@1 z#?MEK$Hxo|4C+)cd$=2s$beY%11mc=F)%Rj4R?X z)GERu07~Pa`7KcVz%ZyCGz89hAndsTQ~^3W1~hLD3sVpcnpY_veCJ0FU?>f|)vj39 zv4hSy2dx1ec-IYW7l6jjKy80e+J|A#xqnxlcm4TuJn{XxUVhXd;o z(0Xz7eH5TD25rjM99XA~+Rnhh5XZp4@B^)%2Feehd@&k8)CmF5K5F!FWl$d2!oa{F zP8};oO(7!$MZ(Jahy84McR0hRTjG!Mgf7#J9IaA_D7r#J*a zYYVHetdRrtw?Jb;pfZVKOGXVK7y^n63=C(`%6`x~AJAS_f|^GaP!s~(3=9m@(AHRi z${*0Vs#y#S44^zhkp)9e^O`FR3~R11F&z0M#BlVp0mJdHfefdASvLF^1z`0~k*Kn#OSM?-_=3|K2d1 z`}dpS{Qv)0@wtD$7|#8D&2aY5X@*ljr!pM>>d&zMwFtw(Hw+ACKt(v-$N*jFQh+|* z!_3I?fSHNigqaaE4Te|$s3i450JNrX2WmM4KC~T_2S96Zhvy+$^N%nvL=`YHocOND zaO!6j!`Z**8P5ItfiUm!!`>G{ z49C9~Fr52)gW>%De`Ke7B8T1YT(7w8ZSn2`L+Bwi!t3$s{df*KML(_Z)hGSpM8P5FKfwz4FPRlsj zKE%oWVmR|>6T|VZh7AAzGcd4$ixALy+~Zin7*zI*p6NjE6pv{iD1P>0i62mdcJYwj ze{uK&1H-;oEDWc9#xR`w`;dC&J%K!O_U|2rlRttO#bgy37@5$|Fb9?Upz|F-bpmE= zjEW3I2!O`qK=Z+%IWbWDAY)MdU^)^8lz{4BC$e$|p3joGONb#vws%@6*2$7|#9sI3Q^s zl>R~Z;{X2)M?U;w@QHiI$iVQ9iIJs{fq`Lko{=gMO*2D4owi&o_s)Xy1E?GWolER8 zDCY?}S1~YL{m;N~{HqPaxqpubH0^`ZKL~^Jz=7AV8A38j*%=sE*%(3705prcQG=-y z0-*Z^JQ)}m(9hNdwN+zE=zkJD-R$oc^_UP^5iO`Uhc9 zTj>1X4Gf3g3osl7Rio7K?5G)Z2m#ReC}^%2v`!9`?vXL5E;z-&z!1j3zyO-pr-O6w zSg{&(-Rpk_hEqQ)7|#FuJE+n>C=ZuW{}~u|Kf@Q^qjG~Y1cVqE7(jQDgYF?mPyaAJXnY$quWw1WeFewAg8IC24Cnq` zAKdBx|9^&af6p--`6NEt4;Y*YoCsHe1~3d57#KizG=T0Tgr$3Q8dS-H&UEsl>7C^% zb&xTi)4$>v&j0^2_|yNtpA5&o1TvflO*Ik`+M`MaYY2eGNkM1!?M7eIi`Gv7-Q9DH zfq?EJVHmV&my4A$r!br+E#09qp# z!@$4*nil|_H;rBo!1$m757ai=$jro&$;`|QI-iL0wU40rt~0--hD6&3mj2KFJ;!kH zoiM|}cSHv9sInmt0-(MD=)9qQIOdOG`QSez10!hc6f~X$+JgYvlj#hdOCWvU0jTeL z>W2-(`Tw7XO!*JmXLItqA;T$9aWMp9cGQWagaBx~6x8Miwf#Z!Wbky4)-FO82aTbE z&Z+|qjMp(RFa$#HFB5~dmq2|E>~6RK8uR~^K;8BH(D6!Gc};!#H^Zr)!3?0hi``eF zqC-CfKpXmu85kIv7#JANGB7ZJ_7oted3bh5fX?g%)e)dQv!Hv8GZ+{cK=)&T z&d34HH&`-(&P+J=rFqDd{jj`r>PG>?t^W)RexPf6hJIv^`T%DLfXZ&rUG<>-f1thh zptIT0&pgAI7trNE>r+AcLFYPtVP;@B$GG+V9Fo5<9 zg2s$MXHfBIJ|!~OpZ44t51f{_rtqkbea1VCqUfzHAQ zwVOceQ9*k#LHDJA)@S=NGcdR@Zn`~j$hH4Zd@o|S`=5a!6x5|BGh{}!jfTLL{|pQ# zzb6l|@t;2oXa9yWfW~e|V{jBu5(1}xf!4k`Fr54MWys`#bAR769QkOZg9 z`akn~E<@D}CI-;iyA*kTRP%@rf!uZmhMWHx7*2dk9i00f(dOhp=j)#M7CE~9dBmq& z>IDF3Px;}GN(`WL*9LrE3oX5)yXVZG%M5#8NHOdKP5V(V2u4jB!6C5tBm;w_A|u0@ z-@Svr4Rq#rBX|$|GSHH!5gccuUZ-vdfc62L`k_B)&iz3z_d)z~|L!mx|Ek7t0<;v6 zy51i(ZG?xw?&k~);kk?qCw~mqwV;0)PW>zcm;Jj&_r8zt)JhxggU&!X_*RU`HhI9X34VpY~_U~hc<6mvT_hPgz z8sU*N>V2Aq!1hNB3{$p&?l{V2IQQ?{faig8|2{Ds`;rXy^XPa#I2c9=8ixRAUUl1j zc80Tm8yU|3C;xsgP@k0&eD3czhT~r=81_7889ncZ#&JGsJZcCW|H8m<_yZ@ynLmvK zR1bV+IPtB5Vbw);hE2Co-8RY_4FP(D!1jj>44ZDVGo1VZY74!jTU+St-)9Ubf21%R z|H?W#)=7_u9<>8!2!Q$n>#s909RKRdaOO8?T!@xuLxb*tJjihLvmHatEJlXq=Wse} zRAMv)=oSLa3mF*R{%2s=`%;SGtDrhvDp>I~1N7`iJ4%zncuFezr60f33wZeK#Y+D$tc7 zM7d^E(P#(^Ob9GI#=tOfGZVw1_lgXseOe*lSX4Dkj?ssYCrvN;fA{)5Gl;_(jy zBoTnq1Csa$h%x`c;t(a^MDzgaW^kAyi8ny?g2j4E%qfz6Cj|ff34iz`*zesstqd|9>NdYG7a}fI9~y1Q7@ML;)$J{{L?PNkV)M zcP2;(A`T8Il%nVWND|~=2BZ-A|NlQk927%Pmx0y6#6dBQCJr_goMNCVA=CqeIG6wHAr6WORC6FD9jZ8( zhnY^m;R(u^XjLmj9F#s$(=$RGHQhqQLFpVN+d<+BoG(za9aJ0?0Vu%<6$b?(7Qrky0MicfAWRsVuR*0AsyiY1 z{r~^}KT!PzDL3$l|3M8ONVxrlnvq5cmwoNO$50C>0NgSNAL1ut366XVQz#)nMKn@6Su>{ii|NkF^7yti1fN;b=WDg@b z`X91~kv$2kUqIC@iW;by4;UCx&4hZafq|h8*$oe%9$;W#ut6680Pz4QZA(Cy5D!2r zDo|cPDeIsW8+vUGs|Z1P4Yf>$RGeUOsFNWUG=Loh&dadW43>aY%;35PMf?Le5 zG0cZHx~L!yaS=4Bf;|f%@Qb6wFGK^_ow&qN(ho#EX8M7MgVP~|i9+HM2gMmUL87RJ zgeY?Mf=Pqo5;=Rp#6bxHGx+~wsZ*dj8qlglsPO*>=zSNc1gPAAGEu3|sN7NB0EK`C z0|Ns%RWdR#Ffu5B6Euj$kix(K#!!(_YBU5!Ltqq)hQMeDjE2By2#kinXb6mkz-S1J zhQMeDjE2By2#kinXb6mkz-S1JhQMeDjD`U9Lf}?R48!m29EQ&s8LVHkvgLkeW!eAD z%8LG#ot^z7D?8_Jc6P$gtSq;0**U83a`QR=NBJ{c%rBvy14d06kPx_%n9T71%SVO} zS=r)0v$A9VW@oSdmz{n0Uv~D_|Jm99{%2?ZhvI+#va`Sc%g%oCH#>Xh@9gY?pP5<8 z57ILj-(+SDh}%akB|ilIXJ<3~%+6u>lAX)*J3Bk+Z+7;Xf7#i;p>Yk1b9^)?J>311 zon7-KGfU!sejdZ)#6J19JRmM*(?8NXa6K2-qF*<-@n<} zM}KE$+dT*mW%!n!PB(vyT10vXyiG}A_+MDW@I5oj{BL&lSqkDEB^~_B&VKwSD=X|p zQWE3$j11BpHmZZ>A+V#mmEnI@Cd03+Y@@%~*_Wvm_b_w*WoN(qm7Np(KP8sobyhab z{WEF-X(8|}JDcH4R<`ut?CgWEIHNZGFFX6*kL+x-|GBvglcPu51Ej?e?Nq-`&tSNh zn#TMmE4!E4agJ`@pX}^yU$S!qzGdgo&O@UnlN17fvvU}JWoO&|%g%m-9$z%z|NfPg zmHxk=fZ;)097*mRRZRU5c%Pfc@HDTGX_xN;?o{{@?8E6Q8oOL_TGWjtx*h zMoBaAS5`K|&#Wx1f7#hjVevx;`p55_oB+@`!=>6*(i}Xhnrb1i(l3DFe@+g=@2u=J zwDCNw`IegL;!k$=)c@_V4EHle`vO#pP!bJ#m6gSi85GX&Co5|XHRF~Tv;JjgAOD<{ z&G&hjm zU?^r_V9eoX<4C_05pnr{P7clUJaW1KjR}3v%2xg{8vl4B6o(`u0|Ns$0|SFH0|SFI z0|P@Q0|P@J0|Uc)1_p+c3=9l+85kH|GcYiGVPIhR#lXM-+EoVHG>3s185#f2cXs*r zKR1`I@&7+N`~BbSY~z2~qvb!2I7S!dWnf^?V_;wab(*FyFfbfsU|@L6z`*d0fq?-d zUWpZBV`BQh#mDDAz2pC1cJ}9=xp`KfR_K2Qrz7A zw_;*m)4TltpPl{jPj)tFo&n<9Q4$sc!VC-yo(v2Opgew=fq~&WmiPvx0ni5YD+~+_ z8yFZE+8G!aKm)1{3=9k!3=E87+?>4JY}`Dc_5jRW4EkMKI>Y<4bl$&NS;vvVIN5Y~%^BLd*7A1_p-HILh=-3=9nCq4_zLfq}t{fq_Acfq?;}m#h$b zk(kJEH#U~}Z&ua{dc--h9e=a4FMiI-78&8~e^49Fje&t-5d#Cm3$%O=D$n0AFfi<4 zU|^^M9a_P_zyK;6K=lGy@r|x&vYj2n|NMMV|E~-=Zs@`Oo1MM%Mn)F%lMGPP72RdS zo)2ov1u-x%Y-M0z_>30spf>vv1_p*o1_lOW1_lN$T874-9MIT*j^)4X?DzDDcc>kI ze`jZ>{Lji_sG0)`0$TcTP?^mM?T>FqE8{_V9n_Ye&A`9_Di8SS}-b5S=l^)v$NOJI{raxi@s%LDUE<}KXwKNhA?QG9aO%< z@;vC^Bv3nFe!wCe&EhAeRSbV1Yko~YYjkLs20s1H%8K}3kk4>DAsNjT!-@-v+v#X! zJ*dAvlYxOj8*~ioVDs(M%p8XQ83_!(v$MnhXJ@~qK^pk^H!HjDQ+fu=kKwx~0Ms{g zXJBABgI3mq#_qfr7#P?FTf8H?=WA9r!>jCEra#%)N&mC6UsEv+eE*xB)BZIlm+Qyy z9q;9V*5{yk7g$*j>c2Nb$8nK^deE|eW@a+{OiN|@m6h%PH#_?p*=Zmr=l|dA?EjzA z(%${d%q;kpmBsNRbJ+Ivr5P9)<}olZ{6xz4rx+L*KxF~l+w_zst{2(44FB_U8NO#{ zYy8g6oN0Hp;` z8(>g6m{>=>$jD-Nn4Zb@Ej!2VZ+6ZMP`~{jp8Y`ova|pE&(41OGAU`jBA>t=2FSeR zH%0~qw*l?oV0ajG=IA$O28Mr<3=IEm85sV@Gcf!wWnlPU%fRrzjDg{AA_Li5#w37}yY5C-Ml|1}H@|KAW9-?-AoTLy+n%b@FS zz=?{1f$K7^GBP{{XF7 zqp=r2dGQx71H=FAG>&^{o$>bo1JfrV1}4zbTN-<9=ot?h`&8hQa_#Xn{ShX0G` zllS52Y8?Z^Z#D)7P!gx1_lBm?ptZ`NhymegwDkh0um4w$f#LrhI>-NG28MrXkZo49 z_2CdS9kiDLRxod*WW73Jzk}L+e?aZN?{tp;Ukr?Y1HkP-!j2qiDohv{7~VtM0LK^@ z7(i>EX%q&aBIbW4o#P(ThG6^+nm>Xr<)o4KhNiKg@v*1S_y?_d1npU&zE4;{%jUuH z-!LHY&+xwolK&r3-)}?PL`en)22kH06gdwW7#I|3;vFIAG%Vxajsc4QsgU>wZPTKO z4~MFuf(#4{p!qRS{J(^*bEm#vm_dCnkQM)%1|yaSH^+yRMyhJTgd_-FT{o%e>O$)G(?`=RjgZ4UF)6OHH{{Mev28RC+=^Xzr7#RL*kH$aNm_?3%P$S(2 zt17Apg2(>Z7#RMqp>zC$<{CIh;~#4zg3AAW(EJZ7|1GhqqKY6m|AUS@ie_N=|BFs( z;4cHi|1?P4gX$lucw{&k0$K|T+RqLO98miYw5Oeh{sSGC@?DsL;olKD#s3)whX0^- z#5DBNP&FE~9+DB%|2qrK|DZjvv|R_t0vclY&%nU=H-wCJo{+IT2Kc-VA^vv;hJW#p zc^^>n9;)#~D|dtAA9?%-G&n|^@gHO_F#cj-VEoR;!0>M}jmvD5AaqC{94_!G8)24F9)Kc?~Ei?$sb|KF}5i>brgznHazTSs)3DkTvu@;{;?S zXidf+2?mD0(-|24Q@SST2Lr>u`3wwyWg+<hOwqX#IH+I$nt|cnNd|^#TNoI0r!g=v3qp?9M)KV# z6JH2`&aQ)v{e#Xa2Ax%nPdy#vK*l(r6h)x($w7TSP@sd(J)(Wzk0j?2r5JR62gv;( zyajshI#C)&6@l*_fUW%l%>jYNf`?kLXfiM`fYL4~{z2uy=manEG1vy}_kqF=#3o+n z;86)0#Ogxw2k5|38m?g+JRYaeC7>`m1PwP(CmFOCkU}E|Ogrdo&ihcmfF>BC2FxWx z$70Yrk9W{8y8*qEX6U#c)1{42zktfJ6ATOt5|~OxMUXa}`=Q&-1 z`U%v5s7CVZC=)dVKl1B zg%jw`3eY_^L((H?jt1SWwh3AWfcys9&qTMg+R@xHSh=9}UZ8u2K;Z+z4TII8WV#D< zjyvp31JHsq&>7yKJ_4D>4LfZvNb`N5G7@w?;&AdLD6T>Km_ZXup!5wo+iDPuYYvJa z2958mMk*gccUTSeb|1EY1)XaFJKGAB20(2cC2R^um>B3>JkS^!C`>?QB#qbl4QS|s z?qUS>Ye9Ym;gt*w44}1c1L~*&v>ddr6LiiwC@er2G}k*E^FDGA2Qx4*z{WU0o6mb0 z7#Kk9A7nKnfeoq?LH&JD7=Yp)bf*ny-PdsUJ1BjF?ox*J2|;}x(7j!tvk!-k-$Bb= z7aV0|IQp5HVe5S+2336qhQJh3-0#G|zyR9Y35t7ASfrDpd5Eh8&2dj+U|@iyeNg=e zTJthE4+C9zl!4*Te+GsFZ@3vw{xD)V{X2=_%xJR52#V(gP(S}?WMI6-!o;e_0y+nm3p7*$S2q$EpfN7c z`bJP02*RLp0Cdk2sC*b${~!O#z;N&#JHyEz5e#Slo@O}z{}0yq#+430;^+Q8VmR}= zmf^%VQHFZZmD1q5Y?_ePc7oRUMh>h;2i$hhUI$S52bv!Mr2$Y~2?#CK7KGk+#Cod5TgS^mQR{|pD- zK4vhq1?}Yn#r;1921d~Q9;hzB<(iQu4jLZ<^?yNY1wdg0!dDp>7(nwP^ywR%`pLj> z@`n(^**_~O%=4IO0+a^UUHiqLW%7ZMfnhZR0~09CGq5m?CIO^`4jKo@Md}xU(g3KA z1dS2MAgQB1bJsHlhE*4t8P5D}V>ti+FV*88R32RX|DR#=?dyyQWx5P`9n|;3@GucH zJ^;GU8MglwlmP=pRF{Fdr8id^NF%ncCfYuFy4ypr{0iZMh!l3cvg$xV~pmYFIL%xsC|7TzT z;WK|c8P5OzLYMdlrGYblTESc9*Mp`*$anq-(Fs~d2C6VY11X^Rhhb2AdOh^cXwX;! zXiS~7AldeifuVCHBg5IhGwBxhp!h%g_X5MA4`K|5K}YX*~gcukYu#dBx{R4`BbB6Q( zKhQP)L1n<1-$e{p{xdK%FD5S}Mz$_cc>p>Wr;34r0d$cJs4)d93y?8rJPA}*fYJkK zj1g3afcDdZ_Sb{j4#tcO3|frqZ=^6>`2U;k@qhNuRB#($#-5QKla%`&l)pjuM)yO{ z%>|vAh@1}4*`Puf)RzE_#XM&!>fOz7iHz}mP#&fPpZ&X%Var`6hP|L^V9Enw)C2}l zoex?^54vNxgn@x!6$1mqWd;TY&_TzbI7d$hApUR0?3POd694D_uV*;>n}y*l$Z4ao zN3{@O1SJEoX`nhEbjhS4bR4;ufq`Kn0|NtS-PukC1_sdCI!74uyLK^L9Ekk?XDP$f z9ZU?HM$f+n#}@qv&=@1=UPW#u1_n;X-OoZ8&j0^K|N8%LFT<1np#CTQd_HOuY6yVm ze7@^3ocs5h?(u)>XBxxB{|pS*K-CbcvqpJz3IWjg%i$0F3}^oyqig(w<{3_WH)8;e zKhVjmqt;*vfz_887(V}JU^w%;g|6{`_Rj%^18;d54uXcVvAAngh<+ge8iPCe!-V17 zzn64M1AiG#|H@&w`=5bfwC_*9c*bQDXwA=|_bd!&|IlX+;M~7+3@3j`Gn@h~eaGdf zQE|G40H_^!{HqSbxqr85TL%35#&G&qEW^$J40zUu)79&vmN76eYO9N+qPiHvzj)UPSXozw&_+u=5Ek!`VM|4CnvEFoc+6&;qV7phLhhJ7`j1IQAl=fUW)HPT_VACB22G>vqhGSo} z8P5Kl!Eo;1E7H=zzpo5u{_J2l^}~ZEafj7JiH~up)h1dL-`u1~fhc1BwNpZN&0O>e2b2t)%=& z@`U)HwS5Q&gG^&U^1uObOv2?shA|+?gYM2l<})xbBb)b!f%!i&{{w^le~=L{^A0fo z2lHX>WspbV{{aOI$VE{9{$Tw7|34D{0oWWw_#6PMYk-FrNc(>f9}>R*K-&NR2OpLR z3ZOrbc=^Bp5&!=K;*1{<{sZO@VB`M(gYX*|4nX7i;RC2MK!$)Yi2sKHq726W!GMVN z5C8xF|9})~AL{@Ae}F8Hg?|9aJdk>1{*QXFuNXjffiMQ&9!-7_@KM}v4~_+Bu>b%6 z!yYv}uM$~x!$50Q-jd1z@ z4g8?o3QyPn4=|$gKQPFHPND=G1|mR?Lvj!3tZPtt1#>Uhb_V3)6jXR1^Fi4XwKzsG z|37Lz0$B+v5J5ByBd2SS7&;$0pM&H-Amw`~9~=!JHV7l9YY-nfU4!_@=^DiU-+%}% zF#iE47Qw8cL})NDFeETAFo1%WkwF0ziwq1C7#J8pN0oyxL}Zj44S~@R7!85Z5Eu=C z(GVC7fzc2c4S~@R7!83D9|EtlvKcO?R(9y`?ChXFSy^^J zvUB9$<>a#c&o5y3m6bi>!;4x0@Gdip;Yvmh!}qLg#oyW4rT?!6=0@1YV_QGW_ppW%!ewUGP6U`xk+-hG0DW%g%oAGdtVlPj)uL zyCTqa<2bDzm0(~1g*(Hq>}(xSeM)KA!}R~n&R+E?J%jU0M#g9u;|YOJ**OgVvr-uT zW@mT6!j2mBzwGQ!zq7NQ|72z1v3*qPXI2)&kE|^5f7#iWs1feSX8g_0p7=j2p5aAS z&S*g62mw&~{GF9$|1UfH3v$>|gZ(c%`^2}bZ2oT}uWtrACm(bcvN{6;1L!>D5C#SY zP@^1lUV1wNWEK!~&*EeT28OO81A}$HGBb&3=M$`Zk>l-OcJ`Cs+1V<;vxjxx7-=)Rgx1_p*r3=9mQJ1Jf;Fff45rU%WDgAV${ihUFn|NqL&Bq?l>!x<0z z-JhHs!@oJhuKWR={|~xH8FZgK=q~9K&~v_#!x?msBHHxB1H*g< z1_n?*kY!+CASt}jf}i1MRyM=WtSqsA+1VGU9sYl_v-|#Mr82zE$w#+#F!Mp@vxCwy z=&mQw-3_2R6SUyr<;Bm6=4 z5rFPD1>IEyDq}#Gf`FFof!a4b)C_BEW_<$Xf%Fv6I8YXuI}_W$-sU zo8ew8=o&6;whkIG(EWll85kHqcZY$>nwty^44^?1nx%JaKL3`T!|*vfhx2cC_5|qs z7D|}H!j=g7)!*#wkpKDl4BrR$8~~_2>B7Lka2i_HfDXq4)wOEa!iOGWpmyS~>>R$o z+1Wk+v$MYu5!R46%E|fvKPTruXx#Zvc6Ruiyh5h$*@L>z1!{|f90d(XK^B{cp3d_#dySSqC!vWo|Br zq5}p69jMs&$HKtypNE0rKR*M*UoHlgH%tr+9~l^uKwV7?XCU8o59*_CVPIg;#ZWbX z0*^AX82D`a5! zXTZSlla+x16wF{Jf%>nD7$5@Q85kHqcgBJ43k6FKHsVckGQlc2Ew4@&E<3=ID_F);l9 z2-@g@1OH=S`2UiD;om$4#{UKk4D2!t3=Hd_=>&A~E$BWjP&q_(R}O%I$_xw)pbOnW z?KVH!*bmy5^Oui-;eQhY!~gdc3=Ge{GBAFPVPF7lu?0DmYOWj%27&sIAWvOq zU|^7-j)OqMiy$Nad6Hh{fWjOrzRAGwTZ4fCRGv}Cm4n0-BL)Tr&=@!fgT}9@;4Dy; z{s+{q+=jhe#~Qvk>HuW5e|s3Xk1#SYg1TH(aOU7JLXv@j0n~Q~71p4+2P(J5(@sBHMh5AD-}(kzwnzeNlzPZ=2)K;7WM6Mz)D1hl>dl&QB;+y-U(#lXPyOPGP- z-$k0Fe}@077#M#sF)$9Rb`WTx4=DT(F)%QI>H`X*6P*4*eXf(V3IBid85BAh7+7CX z;Ko6x6?EJKDEvWveLl)u2Qmgc)&m+_qjK2;Zi_a6%YTMp)dmHPp@RYrv<`;C`6G}q zU|dCm^#7BA;lCdw{3%N%gG@JQ{T(RKs1W`jpMlFD3kHV&Z>byyml+uTOAW*DkB7E@ zKy!MaHIbk|qyU44Q-88EF#KCVQ2Ia2z`(#wx$|y7!|1OS z1H=Dk)QN+CXBZg%%0i}KD0kr?GXd8A+(vQx6Xr3-?+gqqcNiHM{^c<+{Qpj29K2v) z_zxP(V_;x-1G8Z`(V+P}Q2Jj?-7$8Mk^k8l82;BYF#P{YW*od^VE7-y!0>}{n5KQu zI>=Sf0t2)Lp33P8R2TmR)dgt`44{)9!EH!l+NFOP82+7OU;vF_{~fMj4+?8gd;d5z z{6X~qNQ}xDG$!;5R2JzmF#Ma!!0`Veo;jJH3=E$hGBE62!@zi2g@Iu*0|PT?-i69e z8a&2<=71kFFfjaNU|?{gt+W0!FfjdKW?=ZM%E0h1mVx1aF9XAWP#bAI1H=C%kU8Rg zxeN?SCm9$RL2Cl6XzQLqVY)js?4K|&Fo4zw)5%fbx&f5!m>3v9>wx~jmcW44`5XjE zFfcI8VqjnZt>dSYn+C+1YN%iLQ>X1o6+eK+LqG#;pg|rBs+c%94FRox0JS+miLq-C zIT5r65!4p}xoaWS+Qfs%gHWevL-&Y)%6=cH*kGX|85kHq`zJmzFfa@@w^1vMK>I>K zY3vFE0|RItm|Esi!yM51{SDBv5wxaXlp2N(A~Qhk&V5k-f%;#A$Z2q=fc8Mc);iZx zw{HlyW1tvb3=H5wbkJT`yMb~90eeAn(~ZzH0*VJ`0_ul~G^pNP4fWSX1_lPsLF{lb z1_lPuWIL#w1nrlG9tb{&T};?%p!)1HbdSA1VdVp^0@M!!trG^t0cea0RCW%yqsVjs zsBH&JPwNL!o1c((lAwFNK>Y&HSs0+bcZBo|M)@-6K8Dwz%UTAb197+sv_GT;bnFxZ zWbsKk1I_o0U~$R*Hw+BBo-#6=_$J73>ZcyVncuDq=l=OJoc`s)aPo&P!?7;{4BH+s zGVBIzg~iZs4{h&*@^J?P0|Tgj!%#Gc1$Y@47(ja-L4&HG4NRaz(x|s)8=uFId}Lra z@Rph3^e15bTOR!_mbiK|NjK>xqmOf@o@P+sQmyc^I`2BP(5aY!?vL!4Dt_X zlOCup0G+1+8mFUv|8e6j28L_ku-9QY_irD=`Tze2hC6E9{9`!#cNc?!%}QvQ4{Gbj zF;aH)#9)ad&^m!0Xk7p*PeJ2{ps_j%*H1#d*R+6v;mjWfhSNXQ7|#AZL1MTg^2de$ z{~6{T`pY1v`i7B#p_q}8g=Lt8KPaF;{TR@uSy0~$6bGO&bkG`Z&=@vIo_u`lGXuks zk9-Vg|E?iB>|uEU6bBn`9b_!%5oN3eZ51cqS%Xj~s0{(?7lZm{pgIAx_yu%k6=+_A zyf*5trwk0&|1&V0{+$Y~TM_9Q7N(@q7ykcXIP*J>;l_UkhRt^eVK7qcB+x!pP!|7il4CnrlU+!RwpL2f?GaUaa$Z%pYsM&z* z>|w_Sja`HGmV?S6P#YB#51?}C1|tI_sQ&=ka|k-G19Po6sLcZ^d(Qo9qqyA17EfpY z)Pvhp3y%%EK*!=CP=5_H&I{@}pM@=r;}bjgZx_RfZ|n>wKu2^9i?Ait6QE8%=nPN~1_lPu7!v4Q z57537&>m3Gd3_%kIe8v4th@GvcH#e>eHholq{auR{R$et0PQ^o!Lq{%8I)LizxpV_=70@KMD_p!VnK zUw+WBP0IUT@cQ82H-^){JQ&V`sv4@eba)y9>K~o>CJG*7hKC&$>f&?%jxikjBE)bE zG;}^ZgN`b`I{uY`;S#tE%3?VG|2LJ&p-}S(4o|P9ydWe$le{eqd3z~nFV>t6?GQ;_QZ;6VB zbN}8joc%L_;lwu?h7120M%Z`*>ETRB$I0&u49C8(GMxTp&2aYbWQKG9E-{?{_W?RT z@E1&<`}dyV+`o$q=l)J)IQ7$#VgGAZh9jWm!-NbUl_xp`j)LdKm>5oc7h^d6%aq~F zA76%(Kf=KDsh=hc$G(U%9C*XTa2Rw{CefyjDjW@g(GVC7fzc2c4S~@R7!85Z5Eu=C z(GVC70lXpbpHAQ(1Hz&o4EAu&2L^sP`vC(xoP7X=%?uZBU|__`Wi_@${|DsX9}EXTcKm1L|Ns91%;Eh1|3eM? z|DPXZ3CNiL|Npmx90Cm_FdHuZj{#yiNDVIbAGn&s|Np=P2`?MumH+?$AO4Rp6N3$P z!(ouykl9do9tOD|%5Dew3lsnlzdT?71p|cr1Bv~g1DYt>L7@a?gN*nC)eB<(LuP{n z{{M#vLEM1MhG=UBhw~q>aR&xjNP4fX#SV9EA@JR5=f3mZULHDBm$<8+W zm7OE`DLa?>>)^gCkL+Omo|D6HsjUv~COe11og`}j9IJK=v$2E&Ie(AA7YSv4pXf$q`#ot-WF zFFX4jlK=4D;RsUqFFX70@2qTX(0#yz(qlw8>P==A!~g7T(0$VRAU~0c|7K^m{m)Ee z_>u!U(ufEP2agia`d`rcc$UpRew=@^vbK`ycaZvj+1V$5WoHWv(tDdhds0DXc7VDp zpmpbjc(0o|QENTL>WE&ymf zcN_x)18CFb83qOh(B(X!5tNq<42+<=V?p-~fX)H|-Mg__f{W|Gjp*oaWcvTrpX?m1 zzXRJQ0L3%tZcUJ%L1&wQ7MX(%Ah^iDz_5UUfdRBv)|G*QL6eDzS)7T93A9fNv_}(^ z{+ag&28#X7$|9-kgQbyw+1dAgXJ^ZS%FqFbe~`abp6IgUW8uLhc>V zb9Bh|H!QI3$Hg)H%Lcdql>cRCAHW+1pu59!b3u29pZ}YkZ2*e<7wH)=qi9Kk&cp+i z<)A(PMGOq!F>tEc2fD`_REPb^&Q|=Jojvb=cJ?Q1;qWve;s0_sw->tu0xX-&%oslB z=9B3LP|^hr@Pp0>|F6Qp@ZW)f;lCRL!(USdhQA^V44`wXL0h=NXZ1udFff4jO@jOn zI?D%C?vZIPer?~fvKhW+Wpn(=&UOUdD-TNJ|Fg4?|I5za^FKFtLb{I5WF|&NkmD&h zyAL#q{GSziw*MLihX0Ql82*1_U;vFG|Np?i@c#k>!~aGGh9^o43=FBzd=A=64dUbX z0F`8)=j1Uw%E@Q^o|Vo1Co7xxOGXB$+yR-c32je+?oI&lNyH$(|Knv~_}@zSKI+SN z7#Ji#`}!9$FffV^l52QDd*(oAEg-*x84b+b&;oeqo{996RH}Oga z#$U_~jG*H@hzxStDFgK@uQD(&fX=WW(iU+3mtbJ{e}&BWF9YZQ!EpyO$p4_T%cl|P zdB`zvjG#2|uY(N#KVe|_uM6=%@ix*>B`E!a&TRnQpG$%@pfvCo)YiX5LKuMV1FB}aI_t_%$S?-LOQe;644&u3uxFEEJW9u%;kb`PljO9OF8 z!=O9>I)ddN=#IKw&^`}(d*~kn!|xXi41YlPcL_k+Js^Fg*+fItpmVH2?H|yXG8xwV zWME(fwTJ(L{1(W-@E>$H(@q8k(2An}s~8xzu3})YKEc2ssL8-ULKB+|myoFibbbqH zOc^w;PNoqsZJ;s%bO#UkzBVofhX0_xy*c#4JJ1>QFolDK28}nZV_;wajkAKzd7_28 zK>bS4cqV9UlL}!>RVRWLe1Y6WLfeNd`^^{_7(ioPlLy&dOrSecL3tl^&LdeKBT^G6 zAArs~2hC%65~+vQ%0TDFZD(L$ILW}kK;eDKL^)NOfq?-uR|UeLv!jU8L_rIE=1<}gDz(8Kz3%W-DG{kh~j|IcIe>)k@ z|Nn{oT!Vja87}^zUHt!tVcO2O45Ct?zMjnh#XGuJK=Tlw1y`UsU(gr? zXf6~~FQKc$%zY`3}=2@4#M-xK;tl=x)?Ml4r-Hu>ZfEz zMn=$mqM&s!h^PUb?R@69FvIzOXGr!xC_F%A;?4gI450D@Vd`LEf!0ERCSpN-5>PmR z=1M{BG*JI347wIXlc8gU2gCXQ#GT^;@-s39)eFbJvV+eE8!W!YbO)%70xGXT^#f>k z4(MEbPquBwP_8}D5gHV3nVFreCe?jMp*)g04-(N)h`JU(g?PfUr zOOOH7ei(#aBic!z^EE+d6rKHBM)Vn{=l@eLfJ)V1EE4|Bv}USp5%v2>rqSKiIqjpz|93|8Hmn9q#e}ANT+W4#+tl z|9{l~0BQaIr~U`%+>if%>LKTyAk(0uD?og78g#q`LLW^3AE)va@UdWA5Mn_dh%P-oNbZlwVm{tbekyXygj=jQyFF&G02FoB3aM zcH{r-?BCe-+(5-X|IN-$1npD*nmu^Z6lmYn-|TG5|Jm8Eq3*=FZ|q-o_Vqv6*$RVT zzc^@5BxrsBG)BtyBO|k&klUg1fB%8@{L?r5LGA>tH*{lQU;vHrPh?&JLaV0YHIBD+BX1I|Hy6%<;myo6eG_MZ2(-m}YA85`OwC4)sR?u1` z(B0F_$i{uo%w+hVlLOj2*bGe{IMY5<9+ZCKKzjy}&7lGtl%7Fr=9WX(XLK_#Fqjat zP5_#JE&gX`zk<3EpC7LN&CZqw?GvPen_)(P=I}uK2tezp`xzJ*)Ilp{VUmPsP`&jv zI|o#L*8R`U{)N>Kpgo&8IiT}f;{NC6Fnpz69iR=p^K>8dNHmo6LCEbec~E%%$iv|gMXkr@ST7kHZd^#6Qo1fgWL;B|Df|G zl0Y2%803ENUWN(=hW~G|_yJUhZDwEqpLqbvFZhk4jx4B+1zH^4M_qT@O>>n$JpP;i@7(iv_W(EevX$%aEp#4}F#!x{3w4Y-V z0|Nu7A4c4HGN5%_Yp*ggoc-$sUB~H)1{3xqtr|de%H)P}MbKuqDuB#_24Q zBtYX;Aq)%*r=jC&pn4KBzk$|%p83tsaPHq8oNkA$7rglY8^gu_!QgvuLF?{Gat_&w zL3^Y?YhOWUgMr#Bp!fjg4bZw+CWcc#L2Kab8P5MFZVl+Ue@ht-e`Kcf-Lath1~f|u zYAb^L06N3(2m=GdN(Kgoi3}A}S20}t54!gdkzQb7j!B4Xgo7ykbvn0~;1`8Uw+NA)~t4dB^74h-l2e<8~K=l(5cIP!_!=?B%# z$UIOwIQ>h2;oQG{M7aMu!`Z)K;5$qQ%eu!y?-{`EIs4a*;r##Sc>Qqh-#ms>Ke-t| zYuu2-V4&F`_Z<5Ss=xfecb1;V$jcuY&i$LhaQc@R_}=7g4+q-CnD&F}F;Msa%x_tS zbAMA9&i$JPURQnYUn9fWzjh2Kzq1dzaL4ooM5Ji}14GMV28QNE5I$&)G=w!uj)nkT z0Aw{0Xru(hLZyF#M>_xiKVT0LZ;*%33^1A>%>Mv74HJC74w(M|d^+a;{|DqD;*9^n z^gs6hV10k&|AWl=!%+`57rf+5o`K;5=<*zS28IXdH0aVOkT^OGRR`1e0dxzKJOcyx zGAVfmhJT=?Rv>pi{{R0U$o&tY^hYTDp!|&{D(ZAW*9{;kllm2FB2mH;> zmiwNS#rTuHYb;QL|5sKP!|Swk#=qIwp8vA5PyEl${sEnn1+C8k-2syF8@!f?{_Ai+ z=k$W+CjVw<+yBqbegbtD`kejef8e!3ISk)3=`z0v4?oa3=)bbFIsRp5ufyRsuv`CS zXJ7b}oh?b@wLqYLy(j|%gDwLD185F3l!1XE#$8S>?@M|*p?f~Q{magF2Ca{PdyYB` z&|Cs&9uu^08FZ!x=newVS+a{67#Kk3$@SSvNiBY#n)(f2`2Ek${{1gII}nr>sN*c~ z*fc17mO|Gwfaci!85kHqXCCk|GqJF6@dz;d&dLJal_B*%JNr5T_r3p{ooz;i@B@v# zfyQG%W5%F4upkBo2GCj@tls{eox|`hJBRUKc6JXw_x;Px-UM26L1FrnU|?VXoyP!* z!$1ZG22g(=tGkc|LF+yKLhf7Hh3>|G+1da9W@TOZmz`|_id+2W%7f-j|A6K<1Q{6q zD>5+r1D&tP!oUC;@@4>?Q3^U205nDlawoDA@UlT=Gsum9va_ZCWoNhi%g#RkKRf&J z|D2r5i(OoA_$ew?s&a8LJg){_Gl0z~@EjOuen^ag;eRy)!~fF^4F4Z7F#Ny4!0>-I z14CI10|NtS9tL!_H7I_u8A4DDR7d>C&SCtWoh|e?J6rL8LxV6U3kzt!J81nDp0zk2 z_x%I84RjXoKTr!26`%aYz>ro8Zfr4#5OfEL3P9_dKz_%u<_0wP26Eqjkp19R8>-tN zEjI>+|9co1{(;VqBhh+-%0Y8fpmlGcG75`gko*1#F);i;gTq}gH-BYd_~!%(LoCLS zF9eELP+5s%P66ybB?gB7pffXJZoxtSi>HD6Kt2Plsfqw z*?^cvP%Z+R%LC;-EGB`%4>b4te<1<)-GY=kApc-7hBBcE3=9l7)|!Bh-Ur$F&y<1T z|6Lqz1g#y&gTyV!Ka@EJRX2DM8|V%S(0l=^6r2ZgAE<2p@5sRL{}AHL=YM}07(TpW zVE6`FBf|l4A6yf)7@#@`bUvXCegi?_0&*ufFN4k_O=4hp8_B?6xSoN5&7Oe)G_8Z* z1Ttk685kHq>IcnRg35PLJBmyr@M;5%)q~0xa_c2f+XPg8g6bi>7Lg$dYR`h^ z^>E}3qD=&?B?rw7g6bloH4&i@bmtK03?54796@tHp!pKenjswZBo^0z_qBgwWH|F% zfZ^<4MTRqfBpFWrU}exW1}WxcU|;~XAE|Xw4=A63+J2z+GJgNg2c13f4>aE_!f@_i z1;hD&rx?!vf5dS9|4oMT{}wVFd}qnPz*x(`z_5ssfr*z9eB}wq$rNMI{4!`h5Y#@k z1P||EGZJzpf-u9me=8Zzqs>iS`2U~b@P{`Ho>8FmqR7Ams(!H;5g*8*BE1~CvcXLO;yCr1Sg0bI4%+Plj`UOBr@PW*m@t zsgpk#7_R+iU^x5Nh5<773OZ*5lomm2SMD>M`&-O#>L=%bxD67^3=E(--b>&$L4pkD z{yH<9`OUsr`TWp8-sNr~^$yG5r4_ z2ciEkK*=o~~j5&9~J+7k0;{;=k#qZj6UA_KRf%&zwGRI&_2!|)Y}&b@(QRA z18UoX#?$y7$0tPp&(8jd#eUFy$k9L9+5DjR0coTFgF5Y?HMfZj3=GZCb}DF2Vah~X z+oO2)*gpQ3ovlQ{SUqTb8DuBOF3>sEpxkWBz`&@?z`zJ<4MtmpmrsSF(7~a&CXW-pPhXTBm8r6KzDhzzsbr5 zjU%JzBftTzwFLDCKxdMJ#x!s$2klV>?YaJ!o$d8MJNwH2?CifFcYI1s{d*%idfWfp zJh6W{pe8%I5#Ul3bZ49g1H=Cw28RDr7#RM?GB9{pK<{=2tv@GfoZ@?SHp86^@Exb} z|FW|q|L5kWPqMSi=4E9CRWVpPb)bIKKQ;!2|4j@G|GyxOv;AUV*zk~n!8U|}VH>)G zN#%p?5SYclzyL}=2)%5et#V+$doeKl|BPfe@;GiSWZxJ_9YQOKEKq+5lwMKCx*7j~ zYBdn<#bGzd9fujHvv&>D?+2ank8nD|{%LsZKh41KkDm(rpFrzuL33&d`ysVC$p489 z4F5rQ5P|%Qj%P72{AZ%V7$(Sm(3l&N{U9bd{RuNL{NI2j3_$za3?Oz>6#pQLKzq+Y z<6$5+5(e21G8?pC8k`4?F);i;%fN8(ECa((7Z5;1Q{IA7&<8Z zld2UoPYo)!K=mD|dSU88^)krs;K4zdAdxhv%&lf%U;s^k;@*D_8oN6F4Ybx(jp5wC zIEHioG8oSNvtroy3e@}uEgk`x0V;!tv=&7fXlxI(H>ibyfdN#|>a|`vk*9@c0YNPGtHL z!`Z*WG#f7hO%tE`nFEh^^e{O0Z#~1QpKRcCg60vbaLwnOS+6LzTe;Anme_&w# zf8Yo6|K>mJ5BUGEAF%&_9E|1vKR*D&_Wz$hsQ-WbL;e5b|Lp&t|1bZ)|3Ci&{{QU< z-B^{_&1HwZj zs6l6o{mjZ@_>-N@{yRIH{ZmdJ!`GZ#s<(Tpt*cd zdj;$v&>Bn7c+S7<>@1|Ush~bJXx(QNNbU!G{Vb@B3c6GAzZ?U@e>(<-|7r{j(x72D z(3v)%&6QZ&FVH?S=fCXiwEx-J+drnJ?(Md=p4V+_%e~nTvwQ=ceV3ih@INn)z01l9)RqGsN(Rb$Aj3iZ4-f{epudjV2l)Gr zf#IJu8GRp6yBV|}64`vv%KWdW=7TCv@Hs3{bwpF3{y5S;UU1lJU|EX<3MbHA*hCu) zRS0S?gYGwhazWw$7t{v>-HrSa;)g%K85sV~WMKHu1L+?@l@dpR&SV3fBZj0JWCwU0 z0F-tz7#KSG7#K`#Alt@4(S@WGKNHl>1@-knX%C-DQ1J^&o1hXGpE`6o&>c7+eW12A zx*R$m)J6lH$pFf~=yD9;Hqq%{ObloLa57x{&&;3>stFiCbJWD%_X=`5XnijzzCe8i zh#jCim_Y5~vwuPNkaRPg|G%H%;{SOJ`JJHk9H6iP>4T^xOoH+YD1(98yJ`#!j4TXG zPlN8a;9)rbZwdra4CntJWH|qCF~hmP<_y>WgYF)~a1vPp zpgs#IeV+LZnlohq?~^?Ji!7rk(e(fSe}@16+Zq1m{{emm(5%=0Kk^Kq%~Bw#p02i21Vi~^N0pnNT~ z%EQC#Uv~CQXnO$EmI(sY)j0HndWpzpri>JZ}wnh03{8G5;PK2wuAOwfZQkmsyG?W|4U#v|NjNU z`Ty@3<{nzYASo}zpbMG?K+}ofg65t;b3s820>Z%zdtaP@kA+owfBoXzdmVi!z-5m&4iLVE69a=e6AOcZ0ffIom4PANg@r*w1H#XcVqjp;Vqs8FfbdrcFfin| zurNqSK=>V;3=HB^Sr`NaAp8s#28Q|bSQt73{M>oDq(Bbk_4IHF0?C3f2O9$eLrl?% zGzJEt1Wy;okcwMx?^e!`4V`)Xl_lGEhwlA+eCPCc z4&UzVyITAEd*!^}A1=JSvnqJy=WCYB?-lcPkPl9*+qlKwp>6$A`&P~04ra`OB6-Vv z*v=?;E)Z(J-<_;6E%6U$=KPI&rZ6?UnY25~H6 zIn!cc$jS@3$BucOS@w+S^IYo)}#xrt{ zB_7y!AVVR)ZKp6ZYl8BIOLOg2&+RQdb7^|cb+uiO zm6#Xz%&Dh~I6s8-e6!tIxawd+>Z6$Wbp^R*hj;B`>anzYd#TTci(!-R-Omdnl+2ek z*Pn6uV{MR{KJT0LY1#7U=2K^Qf6BGl^@r2qOV1W>+f5n_C&F^~us6iGTZ^YD=@~Ch zHt2iYp#Ep2%x8bywK|)WFYEn0A+)rX$;jkyo}$^Vx%=}6}~>DeHq7I9JHI)dj3sLTer61&Ml|yCuuO8IQ>Pequ?ID z%%v5{vl=DeXJ$XV>VI$6H&F>o5r!wKJuf#)rVB2=T6`d1-SlxX-xtVzLo)`{H{AJeE%98hDIY8=g&LJjVE&@p40R z<9^0(>M9?5)I)`n+E2UH#yJkWTMkzlp( zQ2+C%vHO%RczaxbYph`va;#*-o!*tx+!z|VTvaq<_Y1l+Cj24v=KeKFIDb_yMxUz1SME)-_=}FOb{AY6*-V6gF@~aM}1)T*}{F6xx0-P0q*AvqB_!ep}x$OV3Fk zeyb?vJxO#oVr_V+%EZThFYxb~7V-PD?VVSADQEo5>Ho(!`u8LsmXF?2H~FTVV&Ht^ zou8^|e@;?8nYUK||MMe1zb^U6Yx9U>IfH>sO2c)A_w45w;v)Z6ihrnnAl}mT-(q3> z{@9OvRX4u>m+G^bF+bgCj-=#r^F^W${0#keBz?8AT0Zw$12flxoea)%8QqzlGo&+B zhu7cZyu)?JCF4=~_1_0hItOSO8&7}iKGCo^!0Y){A-5@wJ3U*^Pu2diRnAz$ee&7> zr3)9f%71WuF#W)Bj;a6G9|&&#-#9h{@#2eZ7B79Y;_U-xiV^ZjG18_x?zS)@L& zKCqwhwN&i2i5`pz83qqh9uys_u4xcG5Z+k1bcoVFFx zU|6!A^@qU1{rmL=n71=bXX?Iex3jV89lv+O^WqD4z1OXs`j@wlq3^(qG)41;%^$8U zdU1|hVs+s()pL_n9_s&`xu#p5o4aS5y!1Zp&fXh;GahLD+HGsE+EZ*ZyKJW`+i8hI z!TZ|{bhdR0a2#7&n{>PQ>hlHP`vWQs)`-37Z&R=RUQ=|Gb<5W~{?!i|qr(4n_2@I# ztNh7JWX#!@zdPP`@`Te5uAcwi{%fni%~;{a*Nt2c)8aYzOi!FKd*c)J4G+IxFqZG) zJ`~l|%AW8x|L`NF{ySSf-%mZ3-Jp6kJ4lh`#4cW&E*{p&4e|nPU*{NXs`t93K6RVt zrV6wW-GoNGPFpnKyr z(TeO_>MEP8doJ|f_1Q6VKJ&eK-#mGl-8g1#eRnC>pu&4fH_x=ioZST$7uaJuo+Z9n zv$DmIL4A*&t?64~p_`(@jn*&TpN{j6X!bAbV~kHaeTU(#tisC|iY1Sdf8Aah{X1mx zH)SW`ZDudFH%xf1zx$a+g6%`8bxeG|zguP<-QaEI>1}@1baP{nhtJLSfSKRb_gt5M zDo_~_Q_Q^K7n?>M*S#-J5fz(RVzM?E&E0C&zIAEKgG#N$cLzVVOwnI8`Xtg6m{699ID#%ba_By`n3be<_G1^w`)H?xTEFY75kf&6F+8dd&8D; z$xb~pX=~RmkwrphydKZVN+z+L|t%p!lo1UpKJ4TXQ|Q-r6xCb)^(b z%5SB^4eu}6^A#lCdVYmxN{>+kv#Nw>>-Tf{83(d7f2Ti?PPOo@wD*Ud8w>)!J2#DT92Wjk1By_vyufI;ln)XdOB8-u^T-{^aF=^-|@=VvqL2OD4D z-M&=Mdu3+khRPx?>3SaNKEs43_D0ouZ-c=uiF&B1@;o#?$G+{?*4tAobXz(d7&bn> zWwL}%gkjqjo(h%L$LqdtxVSv$cEqpkuJ6m18^09>X=GMe82|IW+@?n9MZZq`ujKJI zVcHS=X$<9@6Xl&ZJ=Z9K@9$@Zf5Ll>rXCE4KDU~QUG)QZ`;vR2^`<{`H$3FD z5Pq;|3%Bi;-sSb@4=_c^#jpkIa;R*Wez@MkCVKg^efPQlS$*)?F>$~9|7kZii6&d~ z91=>{s(SUmlLY?+s|&Llq7z$O{~l{?)zD|nEB~R9=)0@7eCylD+~jK*ZR$ROmdlky>|AW+zL;Z=^bXaxpG>$}8|DVwKCA3sk^ky~RBLz; zyFeK0Jf4z@9rIWZO$uGDo0EO@<%XTxdd}WF;D6|PTBAAhd_|MO-VZ6i&%uJoeLu7yh!0QS>$XmpX*}E2fjOJJGma5Z(h$NSN>h}dd%gbMb)3) zzIqz(lF_=>e|OATC&mQvtH;>OOdFG|#jA`yI6jDZaNyPXX^i*T@>JF|+w0W6*`wrA zd4^jyqRh-?`Qx%ZhfR!+eT$#=Z}sDUkA6>x`*4-t_Q%s{PfzF1*|hWZ%OycC|9&|t z{@dKjdb&aWy)#?7uZxHzBir0o*|EIp4aX<*01+` zKGpK%uNU{U|JH}k9eq==;PA%S9WUpobZUNCn|zsD`_$UC)wQK>ZZxkullJ+1Wl+Mq z&|3+a{+bLtx{`;=9?Glc$VEl}s%^7ReN>)^;z>D6hAYo`BoMj$RIJRsD;oR4Z-hx4rL!P7iLpD-6Gl3deW<^aXN&He zK7R2lypG}U8MoxJAgAo~@b7I$m&=qZ@$1Z*UiPtFbt^+#|KA34p-t@l$}B7#KUw>k z$_3Lz^w_RleDLOx_rIl+=N&r2IpM!z+H$i;*NVRTZGNJ2!p5L{UEb5X3&jq6xafLA z=yiHa*g-F+@WlKF3J+Kw#2&cKvHRsUCYMGwo%vsH_HViM`23!a&JiVzyIq?R& zKF094=TI)USY73bzHXjWuGxogOFZCskn%v~fycHO{d#7_`3Z&}3=%I++%I%*@%Mn^ zVTV|sfA1}i$}8V~vvY?tT z*Bmp8FLSIswtnruy2cGVk9j#IL@?TLmI$$lmg?KTF<<&bi=w|_jy}No}G2rKJ)9tDIC?yuPHgQozSoPFkAinoXSLZ5z}qq zFY{)ownuF-Tf`Z>f_*lVL-DG&3x3-u)=oQ-asK%t&(MG^atk>yF`@#-SWoo$6TJ1CG%N@>>U*9pSedT*)__uC(&C*Dn8JNscm}vu7Bt8`6r%6g~eJfFFn7%s3nMD!mgdqX7MR(zPIK_ zp~t$i?@qRd9Wuf!cKl~tU$bGY?)DEqpU?N$edF*friLq1BiYoWo_qcbejqNfPsB!r z?~R+ui~rSqGuO92c^&Yt{YG*3wRN%Q`0FbIZn?@`n_{Eb{AkZ-uar%eHk$GrGru?P z<+hMH(YRNFC(bH=!8Q($|MRUK)ZdjSoGe$mW-su2NyVKN={x^qufOwCc+%P+h6#?v z@9gsxCd>LdP3!oyZ-OPCh2-|d2FnB{^+XjYu^V%Hx?0?oIM3u=u%CVPowuHuJ6+#y zym)x~vp&z=8NSYp4s3US%D-kk%@jE=D&J>rgj{yo)pofrW-#{VN|3%ADIK}T zOjy)x%H)fpi+j}1t$1^{Cih(QB|X33K!yozyIwq&)(CS;G&5LO@E}*>TcA<5;cA10 z$-L8RUH{eyjspB4F!A8`|4r-8T~OoGSPdF1^K|udS?83{qyZW%V_;xtU<3_EDS)IH z7#x@w7*ZG@7$P!Cj)uT!2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeD zjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mk zz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By z2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1J zhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kin zXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeD zjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mk zz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By z2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1J zhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kin zXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeD zjE2By2#kinXb6mkz-S1JhQMeDjE2A{7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C z(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R z7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7 zfzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu;ss)WGPyh4T_ z**Oec%g$!_pPdb+L2Qutubdo)A33>Hv2WCn(GVC6A@D5=l-?m}{aa2h z+u!VLslVAdMt`%jo&RTNd;iVOcKe&1ZT>eqTj5tu4i_v>{LIc~xKP_V7`!m*hS3lp zIRw6EWPr>1>)CmXf3maH|7B>N;DVfdMuNwVFe8b(84AVc6|RyM=`oE(NfSy}S`va{Pk zY5RY6_J0!an}6Bai+*S482>LTVF2Z&f%MO)-J>BuYzVy0$!GXqmCo=dJICpNcJ^r! z(mY1~_?Mmi@Naf@^5?WP)^DTr0I^Xps%`*7;D2s5!=LPImw(yW4=73dNP0j2&C1I8 zn4ZD(uBdbXeKu6Q>wo6ta$>b~RB$u|CV3=9l>3=9n73=9kkjEqd0EX*th3=9mW3=9kw3=9mG3=9lrdLp7$?^06E(jxu; z&(41NJ3B`Q(}ANRqai?f2(WVTu`)0+iZC!RfYO*J0|NtS;IV^&fnhNN1H*0x28MGC z3=DS|7#N;0FfhDfWMurnz`*dCfq~%*6o2MrWBYVGB;@b^oE(DXIT?B6=ilsXkh>`N z?5GK&A;7@E!@$6x#lXN2!oa}L%E-vLk%58X8Uq8vI|c@Zp9~BP-x(Mf-ZC&S++|>3 zIK{xgunn3QCPUR%F)%O`F)%RXL2;3euyDuo#Kd>BO8y3=FRs7#MyrFfe>#U|_hxz`(GXfq|i$fq@}~fq}u9fq_Arfq_Ai zfq{XSfq{V)djkpKn*aU%p!wUQWR&g1)E6)QWM_l&E5gE2)@TUe2cWjQ90LPG6twKV z2Tkvwb_=LIGn0XVAqm=!7lWp8{3ha&{hXP__%A!V>VJ0jKWgWJf7#hwcm&b~{f zJn-Rfc6Qp=j7+At$;rbu;zm72Q3!zA=%9Y+c?JfCzYGiv&lwmPW-~A_fa(CyxFtn~ zlcM=cdOE}ZoGj4VAKQP~*~iJs1EBGvoSgsvva|2~&CZVfl%2!!BQuK>XN;;HG9mDv zfq?-X|6^ca0AZLoPS1eaFsTd-44`xmQv85{fuW6ofkBsnfq@C9E*eXG&dvd^fBT)C zE&VS$yYXLk_HAPO1YgtB|KEs;{(mDndeh&mEWQ7w#SGuFb7<`1QRC?w0$&*z7(h*O zP?CjXb_Rz3d<+c#1sE9q@h~v_V@1supkW$#k_Jt_g2n>(K+Aj3m^^5#UY)-Fz-`;x z)KpMkfZ=~fJ&0ENo1LBhFFSkXzwGQQ|Fg4S|Ig0;0$l_B4Z0rs_V28$ZK>MYm!-J5 z|1&c(&H?4&cj+0pEg6*=Vj%!e>!9?{$-wa6n1SJcHUq=|Squ#ScQP>iKgq!G{|p1e z|6>dc|2Hu({O@OA_#eZ-@L!FA;TJOl19<(HJOcy61O^6%kI?r00tN;KBSK@70~JtT zva&$^0q|b0&so_lzq7N&{$%Iq{LRj>1+A0&o1N|OH!I8VPga)n*R(XyJTqvl3Y6KO zLgyz2%ITx_QZ)oX)1=_^&&|N_Ka_#t-xdaj|8E!={{JU{|1vQAf5^b_cQykH>36pg z3bX;3PYbtV^1nqvk9*j9RsS<%T~BiI^HRQ@w#VEBKY^mLD2KfM3T!0>G~1H*rD$kLl( z8LUAJ3=F>+7#N-~FfgbNOCOFHPl3mA6c`x(pQ1eNqvVA-3=IFcAj=kpU0|v}(>^E< z1P(jjjVKTOWoBUb-%aJT4{9HMV_^6f3>hODb^*%Gz`y{Sw*yt2bxbU5!_I>v$3y?+ z85sWGp+Wiw<$<*f41b1W;sEslKz$rg`d`At%nBd)9XaV{xcKzH3j@Rd?=(vPHyIfI zgXVsROJE`UqaHNQ#mK<8kBOOebl(?p$P6j=zd#0t|9@zd{vRi2QVEF%;M(O_|1H)g?o~^;WuZ7NjqKkE)d0Z#fmy{|^R+za<^-eOSToq(P_0TLU{!eCL_&% zKwZHf1LA5rTN(g8g9x;bQ(F1tfVBdL9p5TsMH0 zfco^HyIroHGiVNMn0j;6TO@>l3G^HvPD(hvY85YYZ#(Ak9` z47w#MJpmrT>%o8-`PuFvcD2<#^{d5TdPq)ofZ9T!b^>UANO-_}v+EfH!~AbZ$RrjtDd^w26U%K@TBK2iECdkaU0XKLf+*U#tvg{>U<%{cFi^ z?q2}Ixqql7|3T?r zj^W(jbcXZ))-jy_e~01x|4$6(|Nmw<|NkEpgV-M#&i}i`aPHq4hO>W@8BYI_VOV|( zG_e7?aH#@%_W-EuFK1w20IhqVo!duE9*!XZ+8ff&z`*bxdba;+1_lNz21waTUH|TT z1!?1){4U6F?r$N(`G41->HI&#`TxY=zYOR9U1He#qJ%+640Jv(=zND)3=9nEl-@l+ zU5}2MHpD^z)Q$k1^}Gvu9_Ske1_sa>QifE^2cUL6q%E(?aPHp*hV%db5SivN^TnB8 z{~5Yg{b5kh0Nud>I>!{W-+hP$`KWUTVF(C7?=AwJjR;y~^pb&rVHN`egChgvzAy5f zeEb`zov*-f?%x5j(>-z>apC_zhT~rkFx>tx$xsWb3dncbsLs(4Knnp-n+VjV0NwEg zI(!kd=x!ea0|RL66x2^9{XB)EpV=AC{+&aWw2#OK{}|5woy2hBJ81k9%?qR4(GZ|G z1VD32p!@DXcbJ0CQwG%uptcccJy9J41A{y0K3@g~2Hg39nT3G~G*)xww=Ki@e;=uw z{y}Y{HwoQa89nBm0tRvM&zQ2PJRaP}`~><5R_M}gqd{dq z=nmgC3=9mHq4(o~@&jm02Gm{x)f0~y7#MFeFfyHCSbXvV!$qRTcpzyS(Z(UnKl>MS z4<%JSIcnHw2oM=XQEIQzGhVgDP@dZrNp=fFF&z2G3|>b%)WTrYy`v#;{3~RS%ZYEC3}^q?fcpjK{%vD8|NlC} z`TsAVb;Emx^Z#Bjod0*7;oQHi4CnsVGMxQm$#DED2Y4UA$q{jm(P&y3t|74P0RzLy z?~wJ-SO0_BMeGb`eoHW%`K`%t{=X5!`F}Me;7#5GSKq8 z;TkZb{u*&1u>BEeZ767rlmXnH2d$Mp{|~|kwU9|fZ! zFd71*Aut*OqaiRF0;3@?8UmvsK(!F~!HnIg0}S<8B^ns)v5GS=@M9DI$H2gjP5cjT zacmAc0J^*z)0_{WHa!;gAYlef@ed5lKNzrxH!%OlCjO71{y#Q%f`Sa2zaGf{|NkFL zI5dDl5+mF`)PWuN06jbzSik}H15Nx70|UtT|NsA>hW`%+Xhi*iNW%i@15ErMs`vv2 zsKIFB2XKo+4ETo{zXxFM`A;)(s7F!Fhr1umUW9*9)&F2%fCLGe`~Shx0a`fxZvY4X z|NrRX|C=G^poR1Q{|BMsQ0GE;|NsAg0Ac+=7ykfC4*&oEfKU)cAo2r5AygV90>M8Z z;SV(qA_gM=Ksi=U)&i^0CgOWeQ7MNNP z{evBpT_COk@nQHM!~YLhlK=nz4gA=}AAs^HruiUSv6}NAR19EI{~siQRUEs$|8cnU zKX!lp{||BzBz9nah0_0ExeUsOQw@x8CJN&NR%fCp8Rd+I0CEU`E}v}xOJi+|bKT7R>1grDT(Gyc!c2B(7|?%h%6BZUAc?!RSbG5yWXHu;yG-Ss~^ z`^5k3?C1Zpvp+%c%m3Ng=l^AAPy3gh?esl6hxKn(7Q@qo1SC^NnL{xIKym*oJBRmQ zc6Kc^&j0_<&c=t||I5yv{3k0*98^aPML&10y3SLoQ%oVA##Tz;J?rf#DfuV+hfnf^+ z1H)Mc28JUH3=E+7t%T-x69xtbc?JdsK?Vi}4h9AWW*!b6lnUZsc6L3n`5j%|zwGQq z-?MX=M{X3*-RGdN)?;8`sAgbb*w4VgaGrsIVFd#NLly%A11PQq85r<40GODV7(f`L zOpu+O;cs@f_W$hcd+2dX1pnQ?>};1m**SC%gb`>VsC;)~U|?9tz`$^Yfq`K&0|P?} z0|SFH0|NsKCE@WlJBRUKc6P}B?Cd85;~dmZ0O9OxP#-YwS9Uhbq269ftRI1T|1mHy z{3n3n!2-(fb_@&*D;XFVt}`$&%wk|*u!WZ8a7C0beq`q`JV{Ihl>@f_va>fq$9pi^ ze*dzw|G!R7{(mnv_WHle>(<-{~ins{~Z|^ z{%bKX{O4z2_{zY*0BWK$C^9fGOk`kSxD9Qufy#F#T7=o}%uEK*xbE+)Z0^6=*^d9R zvm5?pXRrRBoxT5GcJ_h)+1cyAXJmA_%F3M-U}rzdz`(%!I}23h(84vt(M(X>gY&&K z1H=D(28RE885sUQWnlRKm4V^^PX>nn-xwJFzhq$ef0TjYPYnZuRT2XO!x;t!h64-? z3?2*&4B!qOEkovYaxw!bKZC~fKxyGyPA>DW>>SQtS=n4aGcs8I7Zri52MzdsW?*1& z193(%F(CkodoXroVEBKEf#Lr@28RFtapQmg85lPIVPJ^c!@wXfL5HZv@WiL=9EKk` zx!`%8pIKQ9zcMo!9>m3g)Pee#j~N&kdO(~}OiT!X+>7@7$GItG9=lqvrVEBKV%=mxD!0;c`?;A{hbAiTx$Y65R;C0J?CI*Iobqoyu z|B{{t{>@=v_y-#M8%*Bwg2un!U~<&pbqlEd|5uQK;oocqhX0`Y9M-nOM}zu^n;01W z$TBd1#tjCC@7TmZ)5#2p3=9mQF@VwfpXg}$%fP_!hlhdTUls$y{|k81!EXkJ|2G*J z{?{@v{1ath0OoMK=A&;NkL1|CC}6oAqvXwA@l1_lPuIPk!`Yk0cA z06NzTTGInc4}-;*pnl&w1_p*I1_lO{HAI8Ofy2lhpgDigyzd4E1_oK0`0Lz128L5V z85qv~VPH7>mx1BT9|ndqzZn>ggXW*nZDf>#mPw%bNN_=gu40r=^$-B9?YjuA4~(g1 zIw+pc{bgV{|DS>3#5ZP!vwwIQ&i)l*IP-^};q)&yhRgpM7(i*`5~%G7%I1t>3=9nG zpz9E*=MJiQXjmJh30>=Ti-Cb5nSp@;w5FRp4;oQGwhO>X|7>>UItrrEY9bM1Bz;K6wfx($Pi$`@)7y_WR zyP)-+cR}m7p?w2TTZ}Yg!Dak!5r%XB8X3<2f5>qD|9?FA1H;je>lpMbL2G?L>&HRu ze$wn4RZZ;>FlAt1SOP7F7cnp}fZAicL|Fo=)6e`CWjOb56~p=e|M0{+QkwYppJDTz zFAN>agBf(ph_Yr>5zRvYwEn_@fq`Kj0|UcV1_p+`3=9mQ{T>$3HU3x}zzCXlU}QM= zx0(33hn5W&|Nm#W@P7}(ncqTK3?3C4xDWu17lGOa`Ox+OsGSHZtLH=acKI_fFo4Pn zRR#t|X@;7a+6?FZ-6SsGL*pJ%Km24k`xi9lJ8-@owHaRsfaV8P85kIX7#J9upliiJ zV@Ri=Ye-Kq7}*_UIQ8>0neqRh;q2dRd=40u8&n|xS|bi>7l77{fyxLYhUfwhhO>Vj zks1Gg7|#72o$DV|QB0y^{{LrUIR9@ZdGY`60>ha z)YnI9<73qI=yJ~)&i?ghuyh?XUKw==nIUlTKWO|%jp5wCWen&4e<4s7{9!o%?>NKR zzn%tE5SX}~;{#9Z)|6iNo>|Zs8 zvwy@GPJCwp$2(}A;23C`7ln3?Y9D+d0GbOp`5hGhkhwq5To7n{7&I3MniD(>TH-PI zoIUEI(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c z4S~@R7!85Z5Eu=C(GVC7fsq>m|3|@S2#kinXb23p5ctE6>Y@jXsQd#AsQdPl$J7AB>XZ-&k$^JiV?En9NK)CM%gZ%&h{{-RI z9{@Y;0}>x(HG&VZ`UhN|Qa;2!gn19ZL4~mI2iU0y{(mt4Bi#P~V5x@){tr;lBJux# z+kxRCoy+hu zE1Thab~eMC^vq%H*TLespV>JKf3ve0{-fY5F#lh6HdyXkdir2-?yz#lZ)lkR&CVA4 zmz^E>FFU*We|Gk)|Jm77{$*zu|I5y{`jef_0g8vwIG}FSe@#zk_?ex}{4YB@_;JQJKw_OrGA5&R3I3Tt@=1_oCK2F4r)28J#M28KBd3=E5)cpf7I<7@^7hMB4Y0!yDHBz*p# zlLHQ4Lh2H#_?fp>T)E|I5xUn(p9$-z6hUR)~RtA)JAMVIBhm!wLolhH?f5 z1{($j24MyU21Z;#!NkY}m-?5T9rQmt`wc9NF=#~IIQTa^TL#odg=-#`jK2&F4F7T9 zT`(W>GB7a2F)%Q!V_;yI!N9=a!N9=450iw`j5wnJtmH>lHp8#%Y^HzN*#ZBvvycDJ z&i;WM{{OPG|36Pm{QEW~W%b|eY}Mf%_9$U4%fRs8n}OkfCIiF&JO+mUkqiv~^%xjt zb3xPhN(Kgo1q=)fwhRmmtl+>TllX&Z|BL?1&i4D4on8MgJA2yy?CgnO($b48BqWaT zu(B3|+N$4&b^8+>{vr$v|LYhS{@-9=`2UlE;s1Xq{>#Ad|0M&%ncWNwTE`g}7@|St zIGI6?q3vg8Cb*9aO5YHi#qd8jhv9!Ns5)T)4MOZ;U|`_J&^T-az+o@M!0>+s1H=D+ z&@hLEJre!69dCPPCB_8)Mf(Zxez+pHvrar28I7@BEudO{`VOe z{>jqGp)3px44WAk7>ej*-vC(i78L$XB!vGN28Ms2wkJK@3Ti}w+O~D{uxw!Lc+J4T z@K2k8;r~q{;@~?2!@o=hhJT=Np@(ysq3v8y-+~_Y4Uiq6I{2SG1H=C#3=IE&V~Gb) z8|Wbe!~Y@%hCdt(4FBlkPDVyhU*j;e%|#zO2gH`&pfZSof#I(h1H=Cq28RE$7#RNV zVqo~ckAdOSCI*JS%M1*?0T8Ew`e+aq%}GW^ZD{)kG`>M|>ju_>AE5Au#=`{=%?@ej z_<`Cwpqt5-GB7ak(AkZ}3=9n085kHqeLp%|Jy6#D0@Vkg@Q2dzAcsgYFfi<3U|?uw zU|;}^pMk_^jb#`Z7)~-UFhtSXq9J0w76Svreg+1H8U_Xi4zk^R`WFMk*}n`7=l(G; zoc{-*LF_Ys7#Kc->LJkR9V0sf1H)wKxZW`C`;r`rpg7pdz`!txfq_ASB;6;!GccU} z!@zLiKPdb;8P5GvVL12C9Ez10&iv+N*zp2nBxp>jf`NenbTy+YNMIDBgn$$@{6X>H zLv(orO7mxa^DvzI7te71{|<)p{~t4)|NjY!A2Xc)x1HheyCenykyZu7#QX)toY3D zKMegF?l8E7=rb^by1po;jB+qT05nJjsvj3KFff3|r7Ia27@VNr+(6_ zor}*e!$;&i`A)aQ^=lhV%cQLGfjV^Zynyocrg& zaQYW3xPLahd_OpRbLKY#!}71w1~~fx0|P%yoB<*Jhxs2|%?Eq9 zIUgAQ|A(1>fcgLbe^B!q*pb*s;tV+04M^e#kn}!~N0{@2AHn|L{{R0Un7K`z|*gxb!&V%s&F&==6|8HPGus}-n{2?-S2H3$u0g5EE{ekUdtbY}Wl;|V?4zSv&^c%z3_52MM59;@ zI{P=6fq|h6dMd{(WT6cjA|fxWc35j{>Q|?@IQ`$;s0aQ^JoA3 zVPH6ZpMhbO2?GP-?hazy`zI?KTJ>LQ zc84LYZWRL;uNU|;}+LoEXX_#^M)-bN{Tcm@|BY&i%87o-2%qchGsB z5WJb;++WN*Gkko{z;OB(Bg46W4h-l2uVOg=|2D(<|4$gs|G&U+{$CfvxxaGY^VNov zf6@H{Iy(+j=bilnI_FQC;oLuEhI4;K7*<|@oU?usbjJa@reVehpK}g9UmkR}8>CKT zU_|NsC0zyRSNfYQt# z!2AaJKi~s180!C@2l0|SFN0|SHbqxkrU|Jm6eQQV)Ca}TsH z2cK=!l>@mOyq;2>f#JVB1H*qk1_ndWI;a@%-K-1@pgYd7?gjW?R>t}-J39!pXXt-U z&fep}Ax9QCJ16|l$z`Oj8}XU;pO=B*e+P7J^fv~E|F0Mr4xeOTNCBNfjZY(d?fakX zZ07%Yc^u44Oy&#>40JAh{<||U{Qrq%O>;J0$3rDSd-Xtj6`*VPLFuZ3fq@aU_Xnzi z*3^H{+T#~!?gzy~7#TK$%AG0(1_nq0ONIfYYx&2_!0_LXf#LrK28RFV7#QwfWnj2n z!ocvK95;aSb}<8~8!pZW>8BlO%qZie&!RKa-P z3vLDm2GH~(Xm1-2h93G0fcya3M*)flP&ouzrw$T>xa7ir3x@Omw=tam{}hamecsCu zlF`n<$S46(Fla~t=zaB|JpmC63=D1zsydDg$G#lFSVMd6-!6vd|G5T@%TXNy+Snt; zz`&rvpkZjiaQy3j4ELY^zm?%EXnzH&#zDse<;bL8JolHK;oLu2Fh2X29lZ7wl($h74^j^3E?n^1$$yY?6tsqN^gixE z8d;;x8cHDmSzZKcO@dg%ivCdlAMBq4_F(!41B3=Gy8`WMWPSkVf8hTCq2>Q!rT@tP zgP6mA075r5Kz4yXV1Up+p!EMn2><_KDE*%p8njLegag1EAVJ%rpu6TlYqUUkC?%#* zw~mIuXb6mkz-S1JhQMeDjE2By2#kinXb6mkzz`1s(0mVQjv0o3XA?D5L>FI!+y**J z>R)y?_rL6H;lI%H6o2OA(#3v4mi(VKjp=`OcKHA7?0x^UvoHP6&R!0hZxvZ|gpgJW zv$} zFaraF9RmXcXcz!AnXSvfzyLZMM6}J)Qu$9-*3SRg+2FGS|72zT_@A9!{U|(%4zw;q`hRwI%Ac&Pnig~ONl|KQ zvVXI|XXFuOBWa5MJ3!~2!J|414Da7FFg$j_WyYWE9QeHR{~WNJK=W-hJa6cK3~1B| z3IAbW_zyZ05tkFe;%N@949u+TAT}snx`G(A!T$sq82&dgF#JEo!0_${1H-+^3=IGI zNw5oazKbOZ>Pb`z${RZwAZ|(ntw8~ur2@(ysOEsqRRGNifaW$X{AXa;_X;Ernhyh= zqX|A-2_!%p3@clza;r#yqhV%bZ8TP+%VBi%9VPIeY z%@>38(G!E_;(Qnw82lI*7(i$9NHb(N$upe$yPDzr|DWKwxl=zsGb}pZ%@CE3@B=+v z2mzpY1G&$EAs{J=;q))$bCdovT>QVB;p%@HmCFzZ5KX@R&%$v2e-^{}|K}Ob|9`-6 z{{K$!c|M@IDxwXcp+ZpJxbYt}UnatE{+~Ppc>exB1H)iCa|LuZ8pxfXbrB%?^lusl z$f(gnIRrqnQ=pn*2+{Ho_x|CB&<*Th`X2*?K41qCKky$^gE9O^ryu<92dn>M0a5pl z0cswM{s86w0ZmIW5DFL29201E3A8u{ejWg5wFzjB3517o;uv-NXb6mkz-S1JhQMeD zjE2CV34w3f*$h9jvl)J-)BmhP&{!>KToi^ue0l{8XyNhyygbqW+1Z)@v$GrjXJA&o3(3m1vF=YhEUho(p zXe<@9?*TMM z82-;>VEDg>f#Jd&1_me49TX^Q&>0yR8U9ySGyl)d*1Q@S>0&A-2HFn<3L~%ylo9_c zkjD1@GBErHjfY_v2}*C^v<%^UuN*a zILN7EpgSHw*#e=CY!+W`KPQ#p{J$UIF|~95{xh8Z)kcb86sl!oSad>>;r#z) z4CnvfXE^`=7{mGhpu9<;u_S1pcNjG0#KCa>zZ}E4e}W7bLFt7A{gf&_^#f!-XzUe2 zgYp8UW{x2J;Bf@15C{MN|H1hGe*@$H|I7#f|7ZXC|3CZ3|Nq-B{{R1g`Tu{=p^AS% z2P=XPNrc-78m9nl$^lK#!N(~;vreEf3J@MaaWU%E(GVC7f#DMZ@HQdz+y!d;47B0k ze|EOQ|LknNf7#ib-^f}60;;b;7&He9b|I*34Z5R6ae<4A+rO;r8{l&ba&k&YHy_+~ z1MQntV_?{uv`gl zmx8J=GS(P?!sjm|1H(HJ1_lGr96RWeB}l&o)Q`CEUy@ONl7-sAN^_QsBUI*_j7iCy;IgsJ}|I1)}nfh%~ zuw&2t7h^d8PmJNrA21i>NvdJc{3NJf1M1&^*iKcL} z0IIh@7_`=qROid`@-qDI=wSSpl?57)A)p;p@4zqvs9!ChE+{DRB|W|Ve@@Qa|2a9J zF)lo2{AXlf_;1C)@FIbM!3DHlO-w~lQ1DwuM$(;_mHF4Cnq{VmR^ + inkscape:current-layer="layer1" + fit-margin-top="9.8" + fit-margin-left="8" + fit-margin-right="10" + fit-margin-bottom="9.8" /> + id="layer1" + transform="translate(-47.453609,-27.112369)"> From 0ca71b16b966d65e19f4a80bb398fc55e6cfcb2c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 13:51:14 -0400 Subject: [PATCH 0619/2524] xo-flatstring: docs: + favicon (with sphinx) --- {img => docs/_static/img}/favicon.ico | Bin {img => docs/_static/img}/icon.svg | 29 +++++++++++++++----------- {img => docs/_static/img}/xo-icon.svg | 0 docs/conf.py | 1 + 4 files changed, 18 insertions(+), 12 deletions(-) rename {img => docs/_static/img}/favicon.ico (100%) rename {img => docs/_static/img}/icon.svg (73%) rename {img => docs/_static/img}/xo-icon.svg (100%) diff --git a/img/favicon.ico b/docs/_static/img/favicon.ico similarity index 100% rename from img/favicon.ico rename to docs/_static/img/favicon.ico diff --git a/img/icon.svg b/docs/_static/img/icon.svg similarity index 73% rename from img/icon.svg rename to docs/_static/img/icon.svg index f86f334f..e37851a2 100644 --- a/img/icon.svg +++ b/docs/_static/img/icon.svg @@ -2,9 +2,9 @@ + inkscape:current-layer="layer1" + fit-margin-top="9.8" + fit-margin-left="8" + fit-margin-right="10" + fit-margin-bottom="9.8" /> + id="layer1" + transform="translate(-47.453609,-27.112369)"> diff --git a/img/xo-icon.svg b/docs/_static/img/xo-icon.svg similarity index 100% rename from img/xo-icon.svg rename to docs/_static/img/xo-icon.svg diff --git a/docs/conf.py b/docs/conf.py index 26dc0ec0..0860b3d7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,3 +33,4 @@ pygments_style = 'sphinx' #html_theme = 'alabaster' html_theme = 'sphinx_rtd_theme' html_static_path = ['_static'] +html_favicon = '_static/img/favicon.ico' From 1d18f11678695e71b5efa4f9256421e3201e3e2e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:09:53 -0400 Subject: [PATCH 0620/2524] xo-flatstring: bugfixes + utest expansion --- docs/lessons.rst | 56 ++++- include/xo/flatstring/flatstring.hpp | 124 ++++++++-- utest/flatstring.test.cpp | 333 ++++++++++++++++++++++++--- 3 files changed, 467 insertions(+), 46 deletions(-) diff --git a/docs/lessons.rst b/docs/lessons.rst index 40c39eaf..a5459c6b 100644 --- a/docs/lessons.rst +++ b/docs/lessons.rst @@ -11,7 +11,7 @@ One hurdle we've created for ourselves, is we need both gcc and clang to agree that an expression can be computed at compile-time; otherwise will get false alarms in our IDE (raised by LSP running in the background, which relies on clang). -Must Fully Initialize Memory +Must fully initialize memory ---------------------------- Struggled for a while with the implementation of :ref:xo::flatstring_concat @@ -42,3 +42,57 @@ Correction is to prove to clang that every memory address owned by an empty ``fl flatstring::flatstring() { std::fill_n(value_, N, '\0'); } + +Still need equality comparison alongside spaceship operator +----------------------------------------------------------- + +Had the impression that spaceship operator for :ref:xo::flatstring would be sufficient +to get all six comparison operators: + +.. code-block:: cpp + + template + constexpr auto + operator<=>(const flatstring & s1, + const flatstring & s2) noexcept + { + return (std::string_view(s1) <=> std::string_view(s2)); + } + +We observe this is not the case, at least with gcc 13.1; need to separately define :ref:xo::operator== + +.. code-block:: cpp + + template + constexpr bool + operator==(const flatstring & s1, + const flatstring & s2) noexcept + { + return ((s1 <=> s2) == std::strong_ordering::equal); + } + +Constexpr strict about pointer arithmetic +----------------------------------------- + +Initially attempted to implement :ref:xo::flatstring reverse iterators using char pointers. + +Notice there's an assymetry between reverse iterators and forward iterators. +We can (and do) implement forward iterators using char pointers. +The natural value of ``flatstring::end()`` is a char pointer referring to just past the end of +the string, i.e. to its null terminator. From the compiler's perspective, this is an ordinary +char pointer, just like other iterator values. + +For reverse iterators this isn't the case. The natural value for ``flatstring::rend()`` might +seem to be a char pointer referring to just before the first character in the string. +However this is no longer a valid pointer address -- dereferencing would be undefined behavior. + +In particular, with this implementation, gcc demotes ``flatstring::rend()`` to non-constexpr + +Workaround is to implement a shim iterator class, where representation is pointer to the +character just after the one the iterator position; iterator's ``operator*`` adjusts pointer before +dereferencing. + +This works because gcc can observe that we never dereference a reverse iterator with pointer value +at the beginning of a flatstring. diff --git a/include/xo/flatstring/flatstring.hpp b/include/xo/flatstring/flatstring.hpp index 55d9f1fd..e7233499 100644 --- a/include/xo/flatstring/flatstring.hpp +++ b/include/xo/flatstring/flatstring.hpp @@ -50,10 +50,80 @@ namespace xo { using iterator = char *; /** @brief representation for a readonly iterator **/ using const_iterator = const char *; - /** @brief representation for a read/write reverse iterator **/ - using reverse_iterator = char *; - /** @brief representation for a readonly reverse iterator **/ - using const_reverse_iterator = const char *; + + /** @brief representation for a read/write reverse iterator + * + * constexpr implementation is tricky here, since we can't + * form the address 'just before the beginning of the string' for @p rend() + * without losing constexprness (at least with gcc 13.1) + * + * Instead iterator always refers to the address immediately after its + * real target. This works since @c rbegin() refers to the char just before + * trailing null + **/ + struct reverse_iterator { + public: + constexpr reverse_iterator(char * p) : p_{p} {} + + constexpr bool _has_pointer() const { return p_ != nullptr; } + + constexpr bool operator==(const reverse_iterator & rhs) const noexcept { + return p_ == rhs.p_; + } + + constexpr char & operator* () const { return *(p_ - 1); } + + constexpr reverse_iterator & operator++ () { + --p_; + return *this; + } + + constexpr reverse_iterator operator++ (int) { + reverse_iterator copy = *this; + --p_; + return copy; + } + + private: + char * p_; + }; + + /** @brief representation for a readonly reverse iterator + * + * constexpr implementation is tricky here, since we can't + * form the address 'just before the beginning of the string' for @p rend() + * without losing constexprness (at least with gcc 13.1) + * + * Instead iterator always refers to the address immediately after its + * real target. This works since @c rbegin() refers to the char just before + * trailing null + **/ + struct const_reverse_iterator { + public: + constexpr const_reverse_iterator(const char * p) : p_{p} {} + + constexpr bool _has_pointer() const { return p_ != nullptr; } + + constexpr bool operator==(const const_reverse_iterator & rhs) const noexcept { + return p_ == rhs.p_; + } + + constexpr const char & operator* () const { return *(p_ - 1); } + + constexpr const_reverse_iterator & operator++ () { + --p_; + return *this; + } + + constexpr const_reverse_iterator operator++ (int) { + const_reverse_iterator copy = *this; + --p_; + return copy; + } + + private: + const char * p_; + }; ///@} /** @defgroup flatstring-constants constants **/ @@ -131,22 +201,22 @@ namespace xo { * * @pre 0<=pos<=N-1 **/ - constexpr value_type & operator[](size_type pos) { return value_[pos]; } - constexpr const value_type & operator[](size_type pos) const { return value_[pos]; } + constexpr value_type & operator[](size_type pos) noexcept { return value_[pos]; } + constexpr const value_type & operator[](size_type pos) const noexcept { return value_[pos]; } ///@} /** @defgroup flatstring-iterators iterators **/ ///@{ constexpr iterator begin() { return &value_[0]; } - constexpr iterator end() { return this->last(); } + constexpr iterator end() { return this->last(); } constexpr const_iterator cbegin() const { return &value_[0]; } constexpr const_iterator cend() const { return const_cast(this)->last(); } constexpr const_iterator begin() const { return cbegin(); } constexpr const_iterator end() const { return cend(); } - constexpr reverse_iterator rbegin() { return this->last(); } - constexpr reverse_iterator rend() { return &value_[0]; } + constexpr reverse_iterator rbegin() { return reverse_iterator(this->last()); } + constexpr reverse_iterator rend() { return reverse_iterator(&value_[0]); } constexpr const_reverse_iterator crbegin() const { return const_cast(this)->last(); } constexpr const_reverse_iterator crend() const { return &value_[0]; } constexpr const_reverse_iterator rbegin() const { return crbegin(); } @@ -156,7 +226,7 @@ namespace xo { /** @defgroup flatstring-assign assignment **/ ///@{ /** @brief put string into empty state. fills entire char array with nulls **/ - void clear() { std::fill_n(value_, N, '\0'); } + constexpr void clear() noexcept { std::fill_n(value_, N, '\0'); } /** @brief replace contents with min(count,N-1) copies of character ch **/ constexpr flatstring & assign(size_type count, value_type ch) { @@ -168,7 +238,7 @@ namespace xo { return *this; } - /** @brief replace contents with first N-1 characters of str **/ + /** @brief replace contents with first N-1 characters of @p x **/ constexpr flatstring & assign(const flatstring & x) { for (std::size_t pos = 0; pos < N-1; ++pos) value_[pos] = x.value_[pos]; @@ -176,13 +246,15 @@ namespace xo { return *this; } /** @brief replace contents with substring [pos,pos+count] of str **/ - constexpr flatstring & assign(const flatstring & x, + template + constexpr flatstring & assign(const flatstring & x, size_type pos, size_type count = npos) { std::size_t i = 0; for (; i < std::min(std::min(count, - std::max(x.capacity-1 - pos, - 0)), + (x.fixed_capacity-1 > pos) + ? x.fixed_capacity-1 - pos + : 0ul), N-1); ++i) value_[i] = x.value_[pos+i]; @@ -274,7 +346,7 @@ namespace xo { * strcmp(s, "obey..."); * @endcode **/ - constexpr operator const char * () const { return value_; } + constexpr operator const char * () const noexcept { return value_; } ///@} private: @@ -293,7 +365,7 @@ namespace xo { } template - constexpr Iterator last() { + constexpr Iterator last() noexcept { Iterator p = &value_[N-1]; /* search backward for first padding '\0' */ @@ -426,6 +498,26 @@ namespace xo { { return (std::string_view(s1) <=> std::string_view(s2)); } + + /** @brief equality comparison for two flatstrings. + * + * Example + * @code + * constexpr bool cmp = (flatstring("foo") == flatstring("foo")); + * static_assert(cmp == true); + * @endcode + * + * @note spaceship operator alone isn't sufficient to get this defined, + * at least with gcc 13.1 + **/ + template + constexpr bool + operator==(const flatstring & s1, + const flatstring & s2) noexcept + { + return ((s1 <=> s2) == std::strong_ordering::equal); + } ///@} } /*namespace xo*/ diff --git a/utest/flatstring.test.cpp b/utest/flatstring.test.cpp index 877a8183..197ccfd9 100644 --- a/utest/flatstring.test.cpp +++ b/utest/flatstring.test.cpp @@ -3,13 +3,236 @@ #include "xo/flatstring/flatstring.hpp" #include "xo/indentlog/scope.hpp" #include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/hex.hpp" #include +#include //#include namespace xo { using namespace std; namespace ut { + template + void + flatstring_iter_tests(const String & str, const char * text) { + size_t n = ::strlen(text); + + REQUIRE(str.size() == n); + + /* verify range iteration visits contents in order */ + { + size_t i = 0; + for (char ch : str) { + INFO(XTAG(i)); + + CHECK(ch == text[i]); + + ++i; + } + + REQUIRE(i == n); + } + + String str_copy; + + REQUIRE(str_copy.capacity() == str.capacity()); + REQUIRE(str_copy.empty()); + + /* verify const iteration visits string elements in order */ + { + str_copy = str; + REQUIRE(str_copy == str); + + size_t i = 0; + + for (auto ix = str_copy.cbegin(), end_ix = str_copy.cend(); ix != end_ix; ++ix) { + INFO(XTAG(i)); + + char ch = *ix; + + CHECK(ch == text[i]); + + ++i; + } + + REQUIRE(i == n); + } + + /* verify string overwrite through iterator */ + { + size_t i = 0; + + for (auto ix = str_copy.begin(), end_ix = str_copy.end(); ix != end_ix; ++ix) { + INFO(XTAG(i)); + + *ix = ('a' + i); + + ++i; + } + + REQUIRE(i == n); + + for (i = 0; i < n; ++i) { + CHECK(str_copy[i] == ('a' + i)); + } + } + + /* verify reverse iteration visits string elements in reverse order */ + { + str_copy = str; + REQUIRE(str_copy == str); + + size_t i = 0; + + for (auto ix = str_copy.rbegin(), end_ix = str_copy.rend(); ix != end_ix; ++ix) { + INFO(XTAG(i)); + + char ch = *ix; + + CHECK(ch == text[n-1-i]); + + ++i; + } + + REQUIRE(i == n); + } + + /* verify string overwrite through reverse iterator */ + { + str_copy = str; + REQUIRE(str_copy == str); + + size_t i = 0; + + for (auto ix = str_copy.rbegin(), end_ix = str_copy.rend(); ix != end_ix; ++ix) { + INFO(XTAG(i)); + + *ix = ('a' + i); + + ++i; + } + + REQUIRE(i == n); + + for (i = 0; i< n; ++i) { + CHECK(str_copy[n-1-i] == ('a' + i)); + } + } + + /* verify const reverse iteration visits string elements in reverse order */ + { + str_copy = str; + REQUIRE(str_copy == str); + + size_t i = 0; + + for (auto ix = str_copy.crbegin(), end_ix = str_copy.crend(); ix != end_ix; ++ix) { + INFO(XTAG(i)); + + char ch = *ix; + + CHECK(ch == text[n-1-i]); + + ++i; + } + + REQUIRE(i == n); + } + } + + template + void + flatstring_assign_tests(const String1 & str, const char * text, + const String2 & str2, const char * text2) { + INFO(tostr(XTAG(str), XTAG(text), XTAG(text2))); + + String1 str_copy; + + str_copy.assign(str.c_str()); + REQUIRE(str_copy == str); + + /* verify assignment from C-style string **/ + { + str_copy.assign(text2); + + INFO(tostr(XTAG(str_copy), XTAG(text2))); + + REQUIRE(::strncmp(str_copy.c_str(), text2, + std::min(::strlen(text2)+1, str_copy.capacity())) == 0); + } + + /* verify assignment from prefix of C-style string */ + for (size_t prefix = 0, n_prefix = ::strlen(text2); prefix < n_prefix; ++prefix) + { + str_copy.assign(str); + + REQUIRE(str_copy == str); + + str_copy.assign(text2, prefix); + + INFO(tostr(XTAG(prefix), XTAG(str_copy), XTAG(text2))); + + if (prefix == 0) { + REQUIRE(str_copy.empty()); + } else { + REQUIRE(str_copy.size() == std::min(prefix, str_copy.capacity())); + REQUIRE(::strncmp(str_copy.c_str(), text2, + std::min(prefix, str_copy.capacity())) == 0); + } + } + + /* verify assignment from substring */ + String2 text2_copy; + text2_copy.assign(text2); + + INFO(tostr(XTAG(text2_copy))); + + for (size_t i = 0, n = text2_copy.size(); i < n; ++i) { + /* deliberately letting j extend beyond the end of text2_copy */ + for (size_t j = i; j < n+10; ++j) { + INFO(tostr(XTAG(n), XTAG(i), XTAG(j))); + + str_copy.assign(str); + + REQUIRE(str_copy == str); + + str_copy.assign(text2_copy, i, j-i); + + INFO(tostr(XTAG(str_copy.fixed_capacity), XTAG(str_copy))); + + REQUIRE(str_copy.size() == std::min(j-i, + std::min(text2_copy.size()-i, + str_copy.capacity()))); + REQUIRE(::strncmp(str_copy.c_str(), text2_copy.c_str() + i, + std::min(j-i, str_copy.capacity())) == 0); + } + } + } + + template + void + flatstring_concat_tests(const String1 & str, const char * text, + const String2 & str2, const char * text2) + { + flatstring concat; + + REQUIRE(concat.empty()); + + /* forcing concat to occur at runtime */ + { + concat = flatstring_concat(str, str2); + auto req_str = string(text) + string(text2); + + REQUIRE(::strcmp(concat.c_str(), req_str.c_str()) == 0); + } + { + concat = flatstring_concat(str2, str); + auto req_str = string(text2) + string(text); + + REQUIRE(::strcmp(concat.c_str(), req_str.c_str()) == 0); + } + } + template void flatstring_runtime_tests(const String & str, const char * text) { @@ -22,16 +245,48 @@ namespace xo { REQUIRE(strcmp(str.c_str(), text) == 0); REQUIRE(strcmp(str, text) == 0); - /* verify range iteration visits contents in order */ + String str2 = str; + + { + string str3{str.str()}; + + REQUIRE(::strcmp(str3.c_str(), str.c_str()) == 0); + } + + REQUIRE(string_view(str2) == string_view(str)); + + { + auto cmp = (str2 <=> str); + REQUIRE(cmp == strong_ordering::equal); + } + + { + bool cmp = (str2 == str); + INFO(xtag("cmp", cmp)); + REQUIRE(str2 == str); + + bool cmp2 = (str2 != str); + REQUIRE(cmp2 != cmp); + } + + str2.clear(); + REQUIRE(str2.empty()); + + str2.assign(100, ' '); + REQUIRE(str2.size() == str2.capacity()); + + /* verify entirely ' ' */ { size_t i = 0; - for (char ch : str) { + for (char ch : str2) { INFO(XTAG(i)); - CHECK(ch == text[i]); + CHECK(ch == ' '); ++i; } + + REQUIRE(i == str2.size()); } } @@ -44,8 +299,9 @@ namespace xo { * REQUIRE() calls to do verification that relies on non-constexpr calls such as * strlen(), strcmp() */ -# define LITERAL_TEST_BODY(name, text) \ - constexpr flatstring name{text}; \ +# define LITERAL_TEST_BODY(name, name2, text, text2) \ + constexpr flatstring name{text}; \ + constexpr flatstring name2{text2}; \ static_assert(name[0]==text[0]); \ static_assert(name.at(0)==text[0]); \ static_assert(name.empty() == true || name.empty() == false); \ @@ -54,10 +310,10 @@ namespace xo { static_assert(name.end() != nullptr); \ static_assert(name.cbegin() != nullptr); \ static_assert(name.cend() != nullptr); \ - static_assert(name.rbegin() != nullptr); \ - static_assert(name.rend() != nullptr); \ - static_assert(name.crbegin() != nullptr); \ - static_assert(name.crend() != nullptr); \ + static_assert(name.crbegin()._has_pointer()); \ + static_assert(name.crend()._has_pointer()); \ + /*static_assert(name.rbegin() != nullptr);*/ \ + /*static_assert(!name.rend());*/ \ static_assert(name.size() >= 0); \ static_assert(name.c_str() != nullptr); \ static_assert((name <=> name) == 0); \ @@ -68,14 +324,11 @@ namespace xo { static_assert(!(name > name)); \ static_assert(!(name < name)); \ flatstring_runtime_tests(name, text); \ - REQUIRE(name.fixed_capacity == strlen(text)+1); \ - REQUIRE(name.capacity() == strlen(text)); \ - REQUIRE(name.size() == strlen(text)); \ - REQUIRE(name.length() == strlen(text)); \ - REQUIRE(strcmp(name.c_str(), text) == 0); \ - REQUIRE(strcmp(name, text) == 0); \ + flatstring_iter_tests(name, text); \ + flatstring_assign_tests(name, text, name2, text2); \ + flatstring_concat_tests(name, text, name2, text2); \ static_assert(string_view(name) == string_view(name)); \ - + /* end LITERAL_TEST_BODY */ TEST_CASE("flatstring", "[flatstring][compile-time]") { @@ -92,19 +345,21 @@ namespace xo { /* mostly compile-time tests here */ - LITERAL_TEST_BODY(s1, "h"); - LITERAL_TEST_BODY(s2, "he"); - LITERAL_TEST_BODY(s3, "hel"); - LITERAL_TEST_BODY(s4, "hell"); - LITERAL_TEST_BODY(s5, "hello"); - LITERAL_TEST_BODY(s6, "hello,"); - LITERAL_TEST_BODY(s7, "hello, "); - LITERAL_TEST_BODY(s8, "hello, w"); - LITERAL_TEST_BODY(s9, "hello, wo"); - LITERAL_TEST_BODY(s10, "hello, wor"); - LITERAL_TEST_BODY(s11, "hello, worl"); - LITERAL_TEST_BODY(s12, "hello, world"); - LITERAL_TEST_BODY(s13, "hello, world!"); + LITERAL_TEST_BODY(s1, t1, "h", "abracadabra!"); + LITERAL_TEST_BODY(s2, t2, "he", "bracadabra!"); + LITERAL_TEST_BODY(s3, t3, "hel", "racadabra!"); + LITERAL_TEST_BODY(s4, t4, "hell", "acadabra!"); + LITERAL_TEST_BODY(s5, t5, "hello", "cadabra!"); + LITERAL_TEST_BODY(s6, t6, "hello,", "adabra!"); + LITERAL_TEST_BODY(s7, t7, "hello, ", "dabra!"); + LITERAL_TEST_BODY(s8, t8, "hello, w", "abra!"); + LITERAL_TEST_BODY(s9, t9, "hello, wo", "bra!"); + LITERAL_TEST_BODY(s10, t10, "hello, wor", "ra!"); + LITERAL_TEST_BODY(s11, t11, "hello, worl", "a!"); + LITERAL_TEST_BODY(s12, t12, "hello, world", "!"); + LITERAL_TEST_BODY(s13, t13, "hello, world!", ""); + + static_assert(s1 == s1); static_assert(s1 != s2); static_assert(s2 != s3); @@ -123,6 +378,26 @@ namespace xo { static_assert(s4 > s3); static_assert(s5 > s4); static_assert(s13 > s12); + + /* concat */ + static_assert(flatstring_concat(s1,t1) == flatstring("habracadabra!")); + + /* clear */ + auto s13_copy = s13; + s13_copy.clear(); + + REQUIRE(s13_copy.empty()); + + constexpr auto s13_copy2 = s13; + + static_assert(s13_copy2.size() == s13.size()); + + //cerr << "s13=[" << s13 << "] s13_copy2=[" << s13_copy2 << "]" << endl; + //cerr << xtag("s13", hex_view(s13.c_str(), s13.c_str() + s13.capacity(), true)) << endl; + //cerr << xtag("s13_copy2", hex_view(s13_copy2.c_str(), s13_copy2.c_str() + s13_copy2.capacity(), true)) << endl; + + REQUIRE(s13_copy2 == s13); + } /*TEST_CASE(flatstring)*/ } /*namespace ut*/ From 2b1aa47893da4cffbec755dc3329991c4beb4a10 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:10:06 -0400 Subject: [PATCH 0621/2524] xo-flatstring: github: ubuntu build --- .github/workflows/ubuntu-main.yml | 139 ++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 .github/workflows/ubuntu-main.yml diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml new file mode 100644 index 00000000..c587c94a --- /dev/null +++ b/.github/workflows/ubuntu-main.yml @@ -0,0 +1,139 @@ +name: build xo-flatstring + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + # install catch2, doxygen. see + # [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + + echo "::group::install catch2" + sudo apt-get install -y catch2 + echo "::endgroup" + + echo "::group::install doxygen" + sudo apt-get install -y doxygen + echo "::endgroup" + + #echo "::group::install pybind11" + #sudo apt-get install -y pybind11-dev + #echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: build xo-cmake + run: | + XONAME=xo-cmake + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/xo-indentlog + + - name: build xo-indentlog + run: | + XONAME=xo-indentlog + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: build self (xo-flatstring) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: | + XONAME=xo-unit + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + (cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}}) From 6ce95635b68e5035dac9039203b83cb09a040018 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:14:25 -0400 Subject: [PATCH 0622/2524] xo-flatstring: github: + sphinx + sphinx-rtd-theme --- .github/workflows/ubuntu-main.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml index c587c94a..83e17077 100644 --- a/.github/workflows/ubuntu-main.yml +++ b/.github/workflows/ubuntu-main.yml @@ -34,6 +34,14 @@ jobs: sudo apt-get install -y doxygen echo "::endgroup" + echo "::group::install sphinx + sudo apt-get install -y python3-sphinx + echo "::endgroup" + + echo "::group::install sphinx readthedocs theme" + sudo apt-get install -y python3-sphinx-rtd-theme + echo "::endgroup" + #echo "::group::install pybind11" #sudo apt-get install -y pybind11-dev #echo "::endgroup" From a8dced149c7d2a8b7f729173cf4758d5b2896f36 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:15:29 -0400 Subject: [PATCH 0623/2524] xo-flatstring: github: typo in yaml --- .github/workflows/ubuntu-main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml index 83e17077..ba529a22 100644 --- a/.github/workflows/ubuntu-main.yml +++ b/.github/workflows/ubuntu-main.yml @@ -34,7 +34,7 @@ jobs: sudo apt-get install -y doxygen echo "::endgroup" - echo "::group::install sphinx + echo "::group::install sphinx" sudo apt-get install -y python3-sphinx echo "::endgroup" From 6df49efaa403633181367f350434cfd39fb013a5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:16:48 -0400 Subject: [PATCH 0624/2524] xo-flatstring: bugfix: stale #include in example --- example/ex1/ex1.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index e343002e..ad34c3ad 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -2,7 +2,7 @@ #include "xo/flatstring/flatstring.hpp" //#include "xo/stringliteral/stringliteral_iostream.hpp" -#include "xo/flatstring/experiment.hpp" +//#include "xo/flatstring/experiment.hpp" //#include "xo/stringliteral/string_view_concat.hpp" #include From 24f9346652836ec96112213d7fa9bd23941830dc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:20:12 -0400 Subject: [PATCH 0625/2524] xo-flatstring: bugfix: prune stale example code --- example/ex1/ex1.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index ad34c3ad..f5843db0 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -14,6 +14,7 @@ main() { using xo::stringliteral_compare; #endif +#ifdef NOT_USING static_assert(foo1().x_ == 1); static_assert(foo1().y_ == 2); @@ -67,6 +68,7 @@ main() { static_assert(z8 == 10); +#endif #ifdef NOT_USING static_assert(count_size("0123") == 5); @@ -83,6 +85,7 @@ main() { static_assert(z10 == 5); #endif +#ifdef NOT_USING //constexpr auto z11 = foofn2("0123"); //static_assert(z9 > 22); @@ -97,11 +100,13 @@ main() { static_assert(s10.size() == 9); cerr << s10.c_str() << endl; +#endif #ifdef NOT_SUCCESSFUL constexpr auto s11 = stringlit_make("0", "1", "23", "456", "78"); #endif +#ifdef NOT_USING constexpr std::size_t z9 = stringlit_capacity(s9, s10); static_assert(z9 == 19); @@ -117,6 +122,7 @@ main() { static_assert(s13.size() == 36); cerr << s13.c_str() << endl; +#endif #ifdef NOT_USING static_assert(stringliteral_compare(s1, s1) == 0); From d34855e7fb86e66d4bb542df95fc8e74c14ba66c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 18:26:03 -0400 Subject: [PATCH 0626/2524] xo-flatstring: github: xo-unit -> xo-flatstring ! --- .github/workflows/ubuntu-main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml index ba529a22..6e30f5ed 100644 --- a/.github/workflows/ubuntu-main.yml +++ b/.github/workflows/ubuntu-main.yml @@ -118,7 +118,7 @@ jobs: # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: | - XONAME=xo-unit + XONAME=xo-flatstring BUILDDIR=${{github.workspace}}/build_${XONAME} PREFIX=${{github.workspace}}/local From 6b43254b79227d83041c5746604d08e7540608f4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:48:38 -0400 Subject: [PATCH 0627/2524] xo-flatstring: github: + run unit tests --- .github/workflows/ubuntu-main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml index 6e30f5ed..dc647f52 100644 --- a/.github/workflows/ubuntu-main.yml +++ b/.github/workflows/ubuntu-main.yml @@ -134,6 +134,10 @@ jobs: cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} echo "::endgroup" + echo "::group::run unit tests ${XONAME}" + cmake --build ${BUILDDIR} -- test + echo "::endgroup" + echo "::group::local install ${XONAME}" cmake --install ${BUILDDIR} echo "::endgroup" From b56cd3e39028d99b9013fdd018b3fa49531c6c62 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:52:31 -0400 Subject: [PATCH 0628/2524] xo-unit: docs: comment tweaks --- include/xo/unit/dim_util.hpp | 2 +- include/xo/unit/numeric_concept.hpp | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/include/xo/unit/dim_util.hpp b/include/xo/unit/dim_util.hpp index ad2f3496..0f84b956 100644 --- a/include/xo/unit/dim_util.hpp +++ b/include/xo/unit/dim_util.hpp @@ -21,7 +21,7 @@ namespace xo { * (1usd + 1eur) is well-defined, but (1sec + 1m) is not. **/ currency, - /** a screen price. dimensionless **/ + /** a screen price **/ price, }; diff --git a/include/xo/unit/numeric_concept.hpp b/include/xo/unit/numeric_concept.hpp index 720099a3..f1d32f58 100644 --- a/include/xo/unit/numeric_concept.hpp +++ b/include/xo/unit/numeric_concept.hpp @@ -11,13 +11,9 @@ namespace xo { * * Intended to include at least: * - built-in integral and floating-point types - * - boost::rational - * - std::complex + * - xo::raio * - xo::unit::quantity * - * This implies we don't require T to be totally ordered, - * and don't require (<,<=,>=,>) operators. - * * Intend numeric_concept to apply to types suitable for * xo::unit::quantity::repr_type. **/ From 3dd48ee1e754a7cdab298f0bcfdc42bc4bf450bb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:53:01 -0400 Subject: [PATCH 0629/2524] xo-unit: bugfix: fix static asserts on operator+= operator-= --- include/xo/unit/quantity.hpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 138565d1..c49ab4d5 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -406,13 +406,11 @@ namespace xo { * @pre @p y must have the same dimension as @c *this. * * @param y quantity to add - * @retval this quantity after adding y + * @return this quantity after adding y **/ template quantity & operator+=(Quantity2 y) { - static_assert(std::same_as< - typename unit_type::canon_type, - typename Quantity2::unit_type::canon_type >); + static_assert(same_dimension_v); /* relying on assignment that correctly converts-to-lhs-units */ quantity y2 = y; @@ -427,13 +425,11 @@ namespace xo { * @pre @p y must have the same dimensions as @c *this * * @param y quantity to subtract - * @retval this quantity after subtracting y + * @return this quantity after subtracting y **/ template quantity & operator-=(Quantity2 y) { - static_assert(std::same_as< - typename unit_type::canon_type, - typename Quantity2::unit_type::canon_type >); + static_assert(same_dimension_v); quantity y2 = y; From 39ca2aa9f4dc1beb3b319064a198c50a7c9a1777 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:53:25 -0400 Subject: [PATCH 0630/2524] xo-unit: quantity: + compare() + operator<=> --- include/xo/unit/quantity.hpp | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index c49ab4d5..0df8ec74 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -440,6 +440,61 @@ namespace xo { } ///@} + /** @defgroup quantity-comparisonsupport **/ + ///@{ + /** @brief compare this quantity with another, return 3-way comparison + * + * @pre arguments must be quantities having the same dimension + * + * @param y rhs quantity to compare + * @return signed integer; {-ve, 0, +ve} when @c *this is {less than, equal, greater than} @p y + **/ + template + requires quantity_concept && same_dimension_v + auto compare(Quantity2 y) const { + /* convert y to same {units, repr} as *this */ + quantity y2 = y; + + auto cmp = (this->scale_ <=> y2.scale()); + + return cmp; + } + ///@} + + /** @defgroup quantity-comparison **/ + ///@{ + /** @brief 3-way comparison of two quantities + * + * @pre arguments must be quantities having the same dimension + * + * @param y rhs quantity to compare + * @return std::partial_ordering + **/ + template + requires quantity_concept && same_dimension_v + auto operator<=>(Quantity2 y) const { + return this->compare(y); + } + + /** @brief compare two quantities for equality + * + * Although compiler generates this (due to presence of 3-way comparison operator), + * it flags ambiguous overload when included alongside .h files from std::distribution. + * Look like ambiguity would need to be resolved by a header change + **/ + template + requires quantity_concept && same_dimension_v + bool operator==(Quantity2 y) const { + return std::is_eq(this->compare(y)); + } + + template + requires quantity_concept && same_dimension_v + bool operator!=(Quantity2 y) const { + return std::is_neq(this->compare(y)); + } + + /** @addtogroup quantity-unit-conversion **/ ///@{ /** @brief convert to quantity with same dimension, different {unit_type, repr_type} From 03717ea7e92b73d6476a8d2c33e643230ea81ce4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:53:54 -0400 Subject: [PATCH 0631/2524] xo-unit: fix ratio_floor() + check args to Quantity converter --- include/xo/unit/quantity.hpp | 1 + include/xo/unit/ratio_util.hpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 0df8ec74..588c45ec 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -502,6 +502,7 @@ namespace xo { * @pre @c Quantity2 must have the same dimension as @c *this. **/ template + requires quantity_concept && same_dimension_v constexpr operator Quantity2 () const { /* avoid truncating precision when converting: * use best available representation diff --git a/include/xo/unit/ratio_util.hpp b/include/xo/unit/ratio_util.hpp index 1d7e3199..970068d8 100644 --- a/include/xo/unit/ratio_util.hpp +++ b/include/xo/unit/ratio_util.hpp @@ -13,7 +13,7 @@ namespace xo { template struct ratio_floor { - using type = std::ratio; + using type = std::ratio; }; template From 5f2f9cccaa5bc0eb869ccd299aaeea775bfd4a7b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:54:22 -0400 Subject: [PATCH 0632/2524] xo-unit: utest: + quantity unit tests for compare --- utest/quantity.test.cpp | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/utest/quantity.test.cpp b/utest/quantity.test.cpp index 83cf18ce..b2ade7c3 100644 --- a/utest/quantity.test.cpp +++ b/utest/quantity.test.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace xo { using xo::unit::quantity; @@ -787,7 +788,73 @@ namespace xo { log && log(XTAG(q1), XTAG(q2)); } /*TEST_CASE(rescale2)*/ + TEST_CASE("compare1", "[quantity]") { + constexpr bool c_debug_flag = true; + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.compare1")); + //log && log("(A)", xtag("foo", foo)); + + namespace u = xo::unit::unit_qty; + + auto q1 = kilometers(150.0) / u::hour; + auto q2 = kilometers(100.0) / u::hour; + + CHECK(is_gt(q1 <=> q2)); + CHECK(is_eq(q1 <=> q1)); + CHECK(is_lt(q2 <=> q1)); + CHECK(q1 == q1); + CHECK(q1 != q2); + CHECK(q1 >= q1); + CHECK(q1 <= q1); + + CHECK(q1 > q2); + CHECK(q1 >= q2); + + CHECK(q2 < q1); + CHECK(q2 <= q1); + + log && log(XTAG(q1), XTAG(q2), XTAG(is_gt(q1<=>q2))); + } /*TEST_CASE(compare1)*/ + + TEST_CASE("compare2", "[quantity]") { + constexpr bool c_debug_flag = true; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.compare2")); + //log && log("(A)", xtag("foo", foo)); + + namespace u = xo::unit::unit_qty; + + auto q1 = kilometers(150.0) / u::hour; + auto q2 = meters(30.0) / u::second; + + CHECK(is_gt(q1 <=> q2)); + CHECK(is_eq(q1 <=> q1)); + CHECK(is_lt(q2 <=> q1)); + CHECK(q1 == q1); + CHECK(q1 != q2); + CHECK(q1 >= q1); + CHECK(q1 <= q1); + + CHECK(q1 > q2); + CHECK(q1 >= q2); + + CHECK(q2 < q1); + CHECK(q2 <= q1); + + log && log(XTAG(q1), XTAG(q2), XTAG(is_gt(q1<=>q2))); + } /*TEST_CASE(compare2)*/ } /*namespace ut*/ } /*namespace xo*/ From 8ae770610eaeb3ff2c52086026c2eea08d8cd78f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:54:42 -0400 Subject: [PATCH 0633/2524] xo-unit: build: + GUESSED_CMAKE_CMD --- cmake/xo-bootstrap-macros.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..936a1810 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -4,6 +4,7 @@ if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "pr endif() if (NOT XO_SUBMODULE_BUILD) + message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}") message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") endif() From 30fd9c6d16fd4cb628b07dd140a4caf651b2946e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:55:01 -0400 Subject: [PATCH 0634/2524] xo-cmake: README: minor tweak --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5af84f03..5ef9d025 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ When this completes, point local browser to `xo-unit/.build/docs/sphinx/index.h $ cd xo-unit $ mkdir .build-ccov $ cd .build-ccov -$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. ``` ### LSP support From c9b81d217f0e0a29b296acdeb7641bf520a05a59 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 20:55:19 -0400 Subject: [PATCH 0635/2524] xo-unit: cosmetic doc tweaks --- CMakeLists.txt | 2 +- docs/quantity-class.rst | 12 ++++++++++-- utest/CMakeLists.txt | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fc595582..951644fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ add_subdirectory(utest) add_subdirectory(docs) # ---------------------------------------------------------------- -# provide find_package() support for reactor customers +# provide find_package() support for projects using this library set(SELF_LIB xo_unit) xo_add_headeronly_library(${SELF_LIB}) diff --git a/docs/quantity-class.rst b/docs/quantity-class.rst index 2945026e..9e1d2c01 100644 --- a/docs/quantity-class.rst +++ b/docs/quantity-class.rst @@ -32,6 +32,12 @@ The simplest way to create a quantity instance is to use either * factory functions in ``xo::unit::qty``, see :doc:`quantity-factoryfunctions` * unit variables in ``xo::unit::units``, see :doc:`quantity-unitvars` +Assignment +---------- + +.. doxygengroup:: quantity-assignment + :content-only: + Access Methods -------------- @@ -63,8 +69,10 @@ Support methods for arithmetic operations .. doxygengroup:: quantity-arithmeticsupport :content-only: -Assignment +Comparison ---------- -.. doxygengroup:: quantity-assignment +Support methods for comparison operators + +.. doxygengroup:: quantity-comparisonsupport :content-only: diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 97d1afa1..d88e7450 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -1,4 +1,4 @@ -# build unittest observable/utest +# xo-unit/utest/CMakeLists.txt set(SELF_EXECUTABLE_NAME utest.unit) set(SELF_SOURCE_FILES From 3cc6ba9a664af32839b7860d87ad13e1ab6317a1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 21:23:20 -0400 Subject: [PATCH 0636/2524] xo-ratio: initial commit --- CMakeLists.txt | 57 +++ LICENSE | 29 ++ README.md | 26 ++ cmake/xo-bootstrap-macros.cmake | 15 + cmake/xo_ratioConfig.cmake.in | 17 + example/CMakeLists.txt | 3 + example/ex1/CMakeLists.txt | 15 + example/ex1/ex1.cpp | 194 ++++++++++ include/xo/ratio/numeric_concept.hpp | 39 ++ include/xo/ratio/ratio.hpp | 547 +++++++++++++++++++++++++++ include/xo/ratio/ratio_concept.hpp | 29 ++ include/xo/ratio/ratio_iostream.hpp | 42 ++ 12 files changed, 1013 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_ratioConfig.cmake.in create mode 100644 example/CMakeLists.txt create mode 100644 example/ex1/CMakeLists.txt create mode 100644 example/ex1/ex1.cpp create mode 100644 include/xo/ratio/numeric_concept.hpp create mode 100644 include/xo/ratio/ratio.hpp create mode 100644 include/xo/ratio/ratio_concept.hpp create mode 100644 include/xo/ratio/ratio_iostream.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..077f1267 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,57 @@ +# xo-ratio/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_ratio VERSION 1.0) +enable_language(CXX) + +# common XO cmake macros (see proj/xo-cmake) +include(cmake/xo-bootstrap-macros.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 + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +xo_toplevel_compile_options() + +# ---------------------------------------------------------------- + +add_subdirectory(example) +#add_subdirectory(utest) +#add_subdirectory(docs) + +# ---------------------------------------------------------------- +# provide find_package() support for projects using this library + +set(SELF_LIB xo_ratio) +xo_add_headeronly_library(${SELF_LIB}) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# dependencies + +#xo_headeronly_dependency(${SELF_LIB} randomgen) +# etc.. + +# end CMakeLists.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cae3cb5d --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2024 Roland Conybeare , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. diff --git a/README.md b/README.md new file mode 100644 index 00000000..722ea8bf --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# ratio library + +Header-only, constexpr library providing exact representation for rational numbers. + +Relative to `std::ratio`: +1. Uses `constexpr` instead of creating new types. + This means it can be used seamlessly at runtime. +2. Supports a few more arithmetic operations, + for example exponentiation to integer powers. +3. Provides concept support (with c++20) +4. Requires modern (c++17) support to achieve this + +## Getting Started + +### install dependencies + +- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros + +### build + install +``` +$ cd xo-ratio +$ PREFIX=/usr/local # for example +$ BUILDDIR=.build # for example +$ make ${BUILDDIR} +$ cmake -DCMAKE_PREFIX_PATH=${PREFIX} -B ${BUILDDIR} +``` diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..936a1810 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,15 @@ +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + # default to typical install location for xo-project-macros + set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +endif() + +if (NOT XO_SUBMODULE_BUILD) + message("-- GUESSED_CMAKE_CMD=cmake -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -B ${CMAKE_BINARY_DIR}") + message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") + message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo-project-macros) diff --git a/cmake/xo_ratioConfig.cmake.in b/cmake/xo_ratioConfig.cmake.in new file mode 100644 index 00000000..e5ee1778 --- /dev/null +++ b/cmake/xo_ratioConfig.cmake.in @@ -0,0 +1,17 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +#find_dependency(reflect) +#find_dependency(subsys) +#find_dependency(Eigen3) +#find_dependency(webutil) +#find_dependency(printjson) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 00000000..36200e18 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,3 @@ +# xo-ratio/example/CMakeLists.txt + +add_subdirectory(ex1) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..b7050cfc --- /dev/null +++ b/example/ex1/CMakeLists.txt @@ -0,0 +1,15 @@ +# xo-ratio/example/ex1/CMakeLists.txt + +set(SELF_EXE xo_ratio_ex1) +set(SELF_SRCS ex1.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_dependency(${SELF_EXE} xo_ratio) +xo_dependency(${SELF_EXE} reflect) + +# end CMakeLists.txt diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp new file mode 100644 index 00000000..8aa3c82d --- /dev/null +++ b/example/ex1/ex1.cpp @@ -0,0 +1,194 @@ +/** @file ex1.cpp **/ + +#include "xo/ratio/ratio_iostream.hpp" +#include + +int +main() { + using xo::ratio::make_ratio; + using xo::ratio::ratio; + using xo::ratio::ratio_concept; + using namespace std; + + constexpr auto r1 = make_ratio(2, 3); + cerr << "r1=make_ratio(2,3): " << r1 << endl; // output + + static_assert(r1.num() == 2); + static_assert(r1.den() == 3); + + static_assert(r1.compare(r1, r1) == 0); + static_assert(xo::ratio::detail::op_aux_type::compare(r1,r1) == 0); + + static_assert(r1 == r1); + static_assert(!(r1 != r1)); + static_assert(r1 != 0); + static_assert(r1 != 1); + static_assert(r1 != 2); + static_assert(r1 != 3); + + static_assert(r1 >= r1); + static_assert(r1 <= r1); + + static_assert(r1 > 0); + static_assert(r1 >= 0); + static_assert(r1 < 1); + static_assert(r1 <= 1); + + constexpr auto r2 = make_ratio(2, 4); + cerr << "r2=make_ratio(2,4): " << r2 << endl; // output + + static_assert(r2.num() == 1); + static_assert(r2.den() == 2); + + static_assert(r2 == r2); + static_assert(r2 != r1); + static_assert(!(r2 > r1)); + static_assert(!(r2 >= r1)); + static_assert(r2 <= r1); + static_assert(r2 < r1); + static_assert(r1 > r2); + static_assert(r1 >= r2); + static_assert(!(r1 < r2)); + static_assert(!(r1 <= r2)); + + constexpr auto r3 = make_ratio(2, 3) - make_ratio(1, 2); + cerr << "r3=r1-r2: " << r1 - r2 << endl; // output + + static_assert(r3.num() == 1); + static_assert(r3.den() == 6); + + static_assert(r3 == r3); + static_assert(r3 != 0); + static_assert(r3 != 1); + static_assert(r3 < r2); + static_assert(r3 <= r2); + static_assert(r3 < r1); + static_assert(r3 <= r1); + + constexpr auto r4 = r1 + r2; + cerr << "r4=r1+r2: " << r1 + r2 << endl; // output + + static_assert(r4.num() == 7); + static_assert(r4.den() == 6); + static_assert(r4 > 1); + static_assert(r4 < 2); + + constexpr auto r5 = r1 + 3; + cerr << "r5=r1+3: " << r5 << endl; // output + + static_assert(r5.num() == 11); + static_assert(r5.den() == 3); + + constexpr auto r6 = 3 + r1; + cerr << "r5=3+r1: " << r6 << endl; // output + + static_assert(r6.num() == 11); + static_assert(r6.den() == 3); + + static_assert(r5 == r6); + static_assert(r5 > 3); + static_assert(r5 < 4); + static_assert(r5 > r1); + + constexpr auto r7 = r6 - 3; + cerr << "r7=r6-3: " << r7 << endl; // output + + static_assert(r7 == r1); + static_assert(r7 >= r1); + static_assert(r7 <= r1); + + constexpr auto r8 = 3 - r6; + cerr << "r8=3-r6: " << r8 << endl; // output + + static_assert(r8 == r8); + static_assert(r8 > -1); + static_assert(r8 < 0); + static_assert(-1 < r8); + static_assert(-1 <= r8); + static_assert(0 >= r8); + + constexpr auto r9 = r8 * r8; + cerr << "r9=r8*r8: " << r9 << endl; // output + + static_assert(r9 == make_ratio(4, 9)); + + constexpr auto r10 = r9 * 9; + cerr << "r10=r9*9: " << r10 << endl; // output + + static_assert(r10 == 4); + static_assert(r10.to() == 4); + + constexpr auto r11 = r9 * 3; + cerr << "r11=r9*3: " << r11 << endl; // output + + static_assert(r11 == make_ratio(4, 3)); + static_assert(r11.to() == 1); + + constexpr auto r12 = 9 * r9; + cerr << "r12=9*r9: " << r12 << endl; + + static_assert(r12 == r10); + static_assert(r12 == make_ratio(4, 1)); + static_assert(r12.to() == 4); + + constexpr auto r13 = 3 * r9; + cerr << "r13=3*r9: " << r13 << endl; // output + + static_assert(r13 == make_ratio(4, 3)); + static_assert(r13 == make_ratio(-4, -3)); + static_assert(r13 == r11); + static_assert(r13.to() == 1); + + constexpr auto r14 = r9 / r9; + cerr << "r14=r9/r9: " << r14 << endl; // output + + static_assert(r14 == 1); + static_assert(r14.to() == 1); + + constexpr auto r15 = r9 / r8; + cerr << "r15=r9/r8: " << r15 << endl; // (4/9) / (-2/3) = (4/9) * (3/-2) = 12/-18 = -2/3 + + static_assert(r15 == make_ratio(-2, 3)); + static_assert(r15 == make_ratio(2, -3)); + + constexpr auto r16 = r9 / 2; + cerr << "r16=r9/2: " << r16 << endl; + + static_assert(r16 == ratio(2, 9)); + static_assert(!r16.is_integer()); + + constexpr auto r17 = 2 / r9; + cerr << "r17=2/r9: " << r17 << endl; + + static_assert(r17 == ratio(9, 2)); + static_assert(!r17.is_integer()); + + constexpr auto r18 = r12 / r8; + cerr << "r18=r12/r8: " << r12/r8 << endl; + + static_assert(r18.is_integer()); + + constexpr auto r19 = r18.power(2); + cerr << "r19=r18^2: " << r19 << endl; + + static_assert(r19.is_integer()); + static_assert(r19 == ratio(36, 1)); + + constexpr auto r20 = r17.power(-3); + cerr << "r20=r17^-3: " << r20 << endl; + + static_assert(!r20.is_integer()); + static_assert(r20 == ratio(8, 729)); + + /* verify constexpr working */ + static_assert(ratio(2,3).num() == 2); + static_assert(ratio(2,3).den() == 3); + static_assert(make_ratio(-1,2).num() == -1); + static_assert(make_ratio(-1,2).den() == 2); + static_assert(make_ratio(-2,4).num() == -1); + static_assert(make_ratio(-2,4).den() == 2); + static_assert(make_ratio(1,-2).num() == -1); + static_assert(make_ratio(1,-2).den() == 2); + static_assert(make_ratio(-4,-6).num() == 2); + static_assert(make_ratio(-4,-6).den() == 3); +} diff --git a/include/xo/ratio/numeric_concept.hpp b/include/xo/ratio/numeric_concept.hpp new file mode 100644 index 00000000..02ad5894 --- /dev/null +++ b/include/xo/ratio/numeric_concept.hpp @@ -0,0 +1,39 @@ +/** @file numeric_concept.hpp **/ + +#pragma once + +#include + +namespace xo { + namespace ratio { + /** @concept numeric_concept + * @brief Concept for values that participate in arithmetic operations (+,-,*,/) and comparisons + * + * Intended to include at least: + * - built-in integral and floating-point types + * - big_int from ctbignum + * - boost::rational + * - std::complex + * - xo::unit::quantity + * + * Accepting complex numbers --> we don't require T to be totally ordered, + * and don't require (<,<=,>=,>) operators. + * + * Intend numeric_concept to apply to types T suitable for + * xo::ratio::ratio + **/ + template + concept numeric_concept = requires(T x, U y) + { + { -x }; + { x - y }; + { x + y }; + { x * y }; + { x / y }; + { x == y }; + { x != y }; + }; + } /*namespace ratio*/ +} /*namespace xo*/ + +/* end numeric_concept.hpp */ diff --git a/include/xo/ratio/ratio.hpp b/include/xo/ratio/ratio.hpp new file mode 100644 index 00000000..60aad611 --- /dev/null +++ b/include/xo/ratio/ratio.hpp @@ -0,0 +1,547 @@ +/** @file ratio.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "ratio_concept.hpp" +#include +#include +//#include + +namespace xo { + namespace ratio { + namespace detail { + /** @brief converts ratio to lowest terms when feasible + * + * Falls back to identity function for non-totally-ordered Ratio::component_type + */ + template > + struct reducer_type; + + /** @brief promote value to ratio type **/ + template > + struct promoter_type; + } + + /** @brief represent a ratio of two Int values. **/ + template + requires std::totally_ordered + struct ratio + { + public: + using component_type = Int; + + public: + constexpr ratio(Int n, Int d) : num_{n}, den_{d} {} + + /** @brief ratio in lowest commono terms + * + **/ + static constexpr ratio reduce(Int n, Int d) { + return ratio(n, d).reduce(); + } + + /** @brief add two ratios **/ + static constexpr ratio add(const ratio & x, + const ratio & y) { + /* (a/b) + (c/d) + * = a.d / (b.d) + b.c / (b.d) + * = (a.d + b.c) / (b.d) + */ + + auto a = x.num(); + auto b = x.den(); + auto c = y.num(); + auto d = y.den(); + + auto num = a*d + b*c; + auto den = b*d; + + return ratio(num, den).maybe_reduce(); + } + + /** @brief subtract two ratios **/ + static constexpr ratio subtract(const ratio & x, + const ratio & y) { + return add(x, y.negate()); + } + + /** @brief multiply two ratios **/ + static constexpr ratio multiply(const ratio & x, + const ratio & y) { + /* (a/b) * (c/d) = a.c / b.d */ + + /* if x,y normalized, + * opportunity to cancel common factor between (a, d) or (c, b) + * + * want to do this before multiplying to avoid overflow involving intermediate terms + */ + + auto a1 = x.num(); + auto b1 = x.den(); + auto c1 = y.num(); + auto d1 = y.den(); + + auto ad_gcf = std::gcd(a1, d1); + auto bc_gcf = std::gcd(b1, c1); + + auto a = a1 / ad_gcf; + auto b = b1 / bc_gcf; + auto c = c1 / bc_gcf; + auto d = d1 / ad_gcf; + + auto num = a*c; + auto den = b*d; + + return ratio(num, den).maybe_reduce(); + } + + /** @brief divide two ratios **/ + static constexpr ratio divide(const ratio & x, + const ratio & y) { + return multiply(x, y.reciprocal()); + } + + /** @brief compute integer power of a ratio **/ + constexpr ratio power(int p) const { + + constexpr ratio retval = ratio(1, 1); + + if (p == 0) + return ratio(1, 1); + + if (p < 0) + return this->reciprocal().power(-p); + + /* inv: x^p = aj.xj^pj */ + ratio aj = ratio(1, 1); + ratio xj = *this; + int pj = p; + + while (pj > 0) { + if (pj % 2 == 0) { + /* a.x^(2q) = a.(x^2)^q */ + xj = xj * xj; + pj = pj / 2; + } else { + /* a.x^(2q+1) = (a.x).x^(2q) */ + aj = aj * xj; + pj = (pj - 1); + } + } + + /* pj = 0, so: x^p = aj.xj^pj = aj.xj^0 = aj */ + return aj; + } + + /** @brief 3-way compare two ratios **/ + static constexpr auto compare(ratio x, ratio y) { + /* ensure minus signs in numerators only */ + if (x.den() < 0) + return compare_aux(ratio(-x.num(), -x.den()), y); + if (y.den() < 0) + return compare_aux(x, ratio(-y.num(), -y.den())); + + return compare_aux(x, y); + } + + constexpr Int num() const { return num_; } + constexpr Int den() const { return den_; } + + constexpr bool is_integer() const { return den_ == 1 || den_ == -1; } + + constexpr ratio negate() const { return ratio(-num_, den_); } + constexpr ratio reciprocal() const { return ratio(den_, num_); } + + /** @brief requires component_type is totally ordered **/ + constexpr Int floor() const { return (num_ / den_); } + + /** @brief requires component_type is totally ordered **/ + constexpr Int ceil() const { return floor() + 1; } + + /** @brief reduce to lowest terms + * + * @pre @c Int type must be totally ordered + **/ + constexpr ratio reduce() const requires std::totally_ordered { + if (den_ < 0) + return ratio(-num_, -den_).reduce(); + + auto factor = std::gcd(num_, den_); + + return ratio(num_ / factor, + den_ / factor); + } + + /** @brief reduce to lowest terms, if Int representation admits + * + * Otherwise fallback to identity function + **/ + constexpr ratio maybe_reduce() const { + return detail::reducer_type::attempt_reduce(*this); + } + + /** @brief return fractional part of this ratio + * + * @pre @c Int type must be totally ordered + **/ + constexpr ratio frac() const requires std::totally_ordered { + return ratio::subtract(*this, this->floor()); + } + + /** @brief convert to non-ratio representation + * + * For example: to int or double + **/ + template + constexpr Repr to() const { return num_ / static_cast(den_); } + + /** @brief convert to representation using different integer types **/ + template + constexpr operator Ratio2 () const requires ratio_concept { + return Ratio2(num_, den_); + } + + private: + /** @brief 3-way compare auxiliary function. + * + * @pre @p x, @p y have non-negative denominator + **/ + static constexpr auto compare_aux(ratio x, ratio y) { + /* control here: b>=0, d>=0 */ + + /* (a/b) <=> (c/d) + * (a.d/b) <=> c no sign change, since d >= 0 + * (a.d) <=> (b.c) no sign change, since b >= 0 + */ + auto a = x.num(); + auto b = x.den(); + auto c = y.num(); + auto d = y.den(); + + auto lhs = a*d; + auto rhs = b*c; + + return lhs <=> rhs; + } + + private: + /** @brief numerator **/ + Int num_; + /** @brief denominator **/ + Int den_; + }; + + namespace detail { + template + struct reducer_type {}; + + template + struct reducer_type { + static constexpr Ratio attempt_reduce(Ratio x) { return x.reduce(); } + }; + + template + struct reducer_type { + static constexpr Ratio attempt_reduce(Ratio x) { return x; } + }; + } + + namespace detail { + template + struct promoter_type; + + template + struct promoter_type { + /* to 'promote' a ratio, rely on its conversion operator */ + static constexpr Ratio promote(FromType x) { return x; } + }; + + template + struct promoter_type { + /* to 'promote' a non-ratio, use denominator=1 */ + static constexpr Ratio promote(FromType x) { return Ratio(x, 1); } + }; + } + + template + constexpr auto + make_ratio (Int1 n, Int2 d = 1) -> ratio> + { + return ratio>(n, d).maybe_reduce(); + } + + namespace detail { + /** @brief auxiliary function for binary ratio operations + * + * Support binary ratio operations on combinations: + * - (ratio, ratio) + * - (ratio, U) // where U is not a ratio + * - (T, ratio(U)) // where T is not a ratio + * + * Goals: + * + * 1. Support expressions like + * + * @code + * auto x = 1 + make_ratio(2,3); + * @endcode + * + * 2. promote to wider types as needed + * + * @code + * auto x = make_ratio(2,3) + make_ratio(1ul,2ul); + * static_assert(std::same_as); + * @endcode + * + * 3. avoid interfering with other templates that may overload operator+ + * + * @pre at least one of (Left,Right) must be known to be a ratio + **/ + template , + bool RightIsRatio = ratio_concept> + struct op_aux_type; + + /** @brief specialization for two ratio types **/ + template + requires (ratio_concept && ratio_concept) + struct op_aux_type { + using component_type = std::common_type_t; + + using ratio_type = ratio; + + static constexpr ratio_type add (const LeftRatio & x, + const RightRatio & y) + { + return ratio_type::add(x, y); + } + + static constexpr ratio_type subtract (const LeftRatio & x, + const RightRatio & y) + { + return ratio_type::subtract(x, y); + } + + static constexpr ratio_type multiply (const LeftRatio & x, + const RightRatio & y) + { + return ratio_type::multiply(x, y); + } + + static constexpr ratio_type divide (const LeftRatio & x, + const RightRatio & y) + { + return ratio_type::divide(x, y); + } + + static constexpr auto compare (const LeftRatio & x, + const RightRatio & y) + { + return ratio_type::compare(x, y); + } + }; + + /** @brief specialization for left-hand ratio and right-hand integer value **/ + template + requires (ratio_concept && !ratio_concept) + struct op_aux_type { + using component_type = std::common_type_t; + + using ratio_type = ratio; + + static constexpr ratio_type add (const LeftRatio & x, + const Right & y) + { + /* reminder: adding an integer can't introduce reduced terms */ + return ratio_type(x.num() + x.den() * y, x.den()); + } + + static constexpr ratio_type subtract (const LeftRatio & x, + const Right & y) + { + /* reminder: subtracting an integer can't introduce reduced terms */ + return ratio_type(x.num() - x.den() * y, x.den()); + } + + static constexpr ratio_type multiply (const LeftRatio & x, + const Right & yp) + { + auto gcf = std::gcd(x.den(), yp); + + auto a = x.num(); + auto b = x.den() / gcf; + auto y = yp / gcf; + + return ratio_type(a*y, b); + } + + static constexpr ratio_type divide (const LeftRatio & x, + const Right & yp) + { + auto gcf = std::gcd(x.num(), yp); + + auto a = x.num() / gcf; + auto b = x.den(); + auto y = yp / gcf; + + return ratio_type(a*y, b); + } + + static constexpr auto compare (const LeftRatio & x, + const Right & y) + { + /* note: in c++26 std::signof is constexpr, usable here */ + if (x.den() >= 0) + return compare_aux(x, y); + else + return compare_aux(LeftRatio(-x.num(), -x.den()), y); + } + + private: + static constexpr auto compare_aux (const LeftRatio & x, const Right & y) { + return (x.num() <=> x.den() * y); + } + }; + + /** @brief specialization for left-hand integer value and right-hand ratio **/ + template + requires (!ratio_concept && ratio_concept) + struct op_aux_type { + using component_type = std::common_type_t; + + using ratio_type = ratio; + + static constexpr ratio_type add(const Left & x, + const RightRatio & y) + { + /* reminder: adding an integer can't introduce reduced terms */ + return ratio_type(x * y.den() + y.num(), y.den()); + } + + static constexpr ratio_type subtract(const Left & x, + const RightRatio & y) + { + /* reminder: subtracting an integer can't introduce reduced terms */ + return ratio_type(x * y.den() - y.num(), y.den()); + } + + static constexpr ratio_type multiply (const Left & xp, + const RightRatio & y) + { + auto gcf = std::gcd(xp, y.den()); + + auto x = xp / gcf; + auto c = y.num(); + auto d = y.den() / gcf; + + return ratio_type(x*c, d); + } + + static constexpr ratio_type divide (const Left & x, + const RightRatio & y) + { + return multiply(x, y.reciprocal()); + } + + static constexpr auto compare(const Left & x, + const RightRatio & y) + { + if (y.den() >= 0) + return compare_aux(x, y); + else + return compare_aux(x, RightRatio(-y.num(), -y.den())); + } + + private: + static constexpr auto compare_aux (const Left & x, + const RightRatio & y) + { + return (x * y.den() <=> y.num()); + }; + }; + } /*namespace detail*/ + + /** @brief add two ratios. + * + * One argument may be a non-ratio type if it can be promoted to a ratio + **/ + template + inline constexpr auto + operator+ (const Ratio1 & x, const Ratio2 & y) + requires (ratio_concept || ratio_concept) + { + return detail::op_aux_type::add(x, y); + } + + /** @brief subtract two ratios. + * + * One argument may be a non-ratio type if it can be promoted to a ratio + **/ + template + inline constexpr auto + operator- (const Ratio1 & x, const Ratio2 & y) + requires (ratio_concept || ratio_concept) + { + return detail::op_aux_type::subtract(x, y); + } + + /** @brief multiply two ratios + * + * One argument may be a non-ratio type if it can be promoted to a ratio + **/ + template + inline constexpr auto + operator* (const Ratio1 & x, const Ratio2 & y) + requires (ratio_concept || ratio_concept) + { + return detail::op_aux_type::multiply(x, y); + } + + /** @brief divide two ratios + * + * One argument may be a non-ratio type if it can be promoted to a ratio + **/ + template + inline constexpr auto + operator/ (const Ratio1 & x, const Ratio2 & y) + requires (ratio_concept || ratio_concept) + { + return detail::op_aux_type::divide(x, y); + } + + /** @brief compare two ratios for equality + * + * One argument may be a non-ratio type if it can be promoted to a ratio + **/ + template + inline constexpr bool + operator== (const Ratio1 & x, const Ratio2 & y) + requires (ratio_concept || ratio_concept) + { + return (detail::op_aux_type::compare(x, y) == 0); + } + + /** @brief compare two ratios + * + * One argument may be a non-ratio type if it can be promoted to a ratio + **/ + template + inline constexpr auto + operator<=> (const Ratio1 & x, const Ratio2 & y) + requires (ratio_concept || ratio_concept) + { + return detail::op_aux_type::compare(x, y); + } + + } /*namespace ratio*/ +} /*namespace xo*/ + +/** end ratio.hpp **/ diff --git a/include/xo/ratio/ratio_concept.hpp b/include/xo/ratio/ratio_concept.hpp new file mode 100644 index 00000000..8ca1d438 --- /dev/null +++ b/include/xo/ratio/ratio_concept.hpp @@ -0,0 +1,29 @@ +/** @file ratio_concept.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "numeric_concept.hpp" + +namespace xo { + namespace ratio { + /* also expect: + * Ratio::num_type / Ratio::den_type rounds towards -inf + */ + template + concept ratio_concept = requires(Ratio ratio) + { + typename Ratio::component_type; + typename Ratio::component_type; + + { ratio.num() } -> std::same_as; + { ratio.den() } -> std::same_as; + } && numeric_concept; + + } /*namespace ratio*/ +} /*namespace xo*/ + + +/** end ratio_concept.hpp **/ diff --git a/include/xo/ratio/ratio_iostream.hpp b/include/xo/ratio/ratio_iostream.hpp new file mode 100644 index 00000000..fc898b36 --- /dev/null +++ b/include/xo/ratio/ratio_iostream.hpp @@ -0,0 +1,42 @@ +/** @file ratio_iostream.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "ratio.hpp" +#include + +namespace xo { + namespace ratio { + /** @brief print ratio x on stream os. + * + * Example: + * @code + * print_ratio(std::cerr, make_ratio(1,2); // outputs "" + * @endcode + **/ + template + void + print_ratio (std::ostream & os, const Ratio & x) { + os << ""; + } + + /** @brief print ratio x on stream os. + * + * Example: + * @code + * std::cout << make_ratio(2,3); // outputs "" + * @endcode + **/ + template + inline std::ostream & + operator<< (std::ostream & os, const Ratio & x) { + print_ratio(os, x); + return os; + } + } +} + +/** end ratio_iostream.hpp **/ From 978af419b30bf495863aea6563847c9f6f6b4031 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 17 Apr 2024 21:23:47 -0400 Subject: [PATCH 0637/2524] xo-ratio: + .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..13c0afb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json From 0a03aa949bef07554be809abfdae855e62ffde41 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 13:27:06 -0400 Subject: [PATCH 0638/2524] xo-flatstring: undercap flastring_concat instead of char arrays --- example/ex1/ex1.cpp | 6 +++--- include/xo/flatstring/flatstring.hpp | 13 ++++++++++++- utest/flatstring.test.cpp | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index f5843db0..7f46f432 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -136,12 +136,12 @@ main() { static_assert(sizeof(s14) == 7); constexpr flatstring s15 = flatstring_concat(flatstring("hello"), - ", ", - flatstring("world")); + flatstring(", "), + flatstring("world")); static_assert(s15.fixed_capacity == 13); static_assert(sizeof(s15) == 13); - constexpr auto s16 = xo::flatstring_concat("foo", "bar"); + constexpr auto s16 = xo::flatstring_concat(flatstring("foo"), flatstring("bar")); static_assert(s16.fixed_capacity == 7); diff --git a/include/xo/flatstring/flatstring.hpp b/include/xo/flatstring/flatstring.hpp index e7233499..49db3465 100644 --- a/include/xo/flatstring/flatstring.hpp +++ b/include/xo/flatstring/flatstring.hpp @@ -452,7 +452,18 @@ namespace xo { std::size_t pos = 0; auto detail_concat = [ &pos, &result ](auto && arg) { - constexpr auto count = (sizeof(arg) - sizeof(value_type)) / sizeof(value_type); + /* tradeoff here: + * 1. flatstring::size() is constexpr, so we can concat strings with size() < capacity(). + * (note flatstring::from_int() likely creates such strings) + * 2. ..but no size() method on char arrays. + * 3. std::size() not suitable: size of char array includes null terminator, + * while flatstring.size() excludes it, and flatstring behavior is consistent with + * std::string.size() + * Consequence of using arg.size() here; have to wrap char arrays with + * flatstring() to use them with flatstring_concat() + */ + auto count = arg.size(); + //constexpr auto count = (sizeof(arg) - sizeof(value_type)) / sizeof(value_type); std::copy_n(/*arg.c_str()*/ static_cast(arg), count, result.value_ + pos); pos += count; diff --git a/utest/flatstring.test.cpp b/utest/flatstring.test.cpp index 197ccfd9..8a3761a1 100644 --- a/utest/flatstring.test.cpp +++ b/utest/flatstring.test.cpp @@ -231,6 +231,25 @@ namespace xo { REQUIRE(::strcmp(concat.c_str(), req_str.c_str()) == 0); } + +#ifdef NOT_USING + { + auto concat4 = flatstring_concat(str, + flatstring(text2), + str, + flatstring(text2)); + auto req_str = string(text) + string(text2) + string(text) + string(text2); + + REQUIRE(::strcmp(concat4.c_str(), req_str.c_str()) == 0); + } +#endif + + { + auto concat4 = flatstring_concat(str, str2, str, str2); + auto req_str = string(text) + string(text2) + string(text) + string(text2); + + REQUIRE(::strcmp(concat4.c_str(), req_str.c_str()) == 0); + } } template From ffeb8739079aebd1e3ada7f0156a288eb6f90ce2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 13:28:33 -0400 Subject: [PATCH 0639/2524] xo-flatstring: + from_int() named ctor --- include/xo/flatstring/flatstring.hpp | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/include/xo/flatstring/flatstring.hpp b/include/xo/flatstring/flatstring.hpp index 49db3465..41bfffa3 100644 --- a/include/xo/flatstring/flatstring.hpp +++ b/include/xo/flatstring/flatstring.hpp @@ -168,6 +168,38 @@ namespace xo { } ///@} + /** @brief construct from integer **/ + static constexpr flatstring from_int(int x) { + constexpr size_t buf_z = 20; + + bool negative_flag = (x < 0); + std::size_t i = buf_z; + char buf[buf_z]; + std::fill_n(buf, N, '\0'); + + if (negative_flag) + x = -x; + + buf[--i] = '\0'; + + if (x == 0) + buf[--i] = '0'; + + while ((i > 0) && (x != 0)) { + buf[--i] = ('0' + x % 10); + x = x / 10; + } + + if ((i > 0) && negative_flag) + buf[--i] = '-'; + + char retv[N]; + std::fill_n(retv, N, '\0'); + std::copy_n(buf + i, buf_z - i, retv); + + return retv; + } + /** @defgroup flatstring-properties property-methods **/ ///@{ /** @brief true if (and only if) string is empty **/ From 8a020258c437cb3ca6d7520b51ea99da3de18167 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 13:28:51 -0400 Subject: [PATCH 0640/2524] xo-flatstring: utest: skip ccov target in submodule build --- utest/CMakeLists.txt | 59 ++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 091e56ef..1779b465 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -14,36 +14,41 @@ add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) # ---------------------------------------------------------------- # in coverage build, target to build+install coverage report -set(CCOV_OUTPUT_DIR ${PROJECT_BINARY_DIR}/ccov/html) -set(CCOV_INDEX_FILE ${CCOV_OUTPUT_DIR}/index.html) -set(CCOV_REPORT_EXE ${PROJECT_BINARY_DIR}/gen-ccov) -# CMAKE_INSTALL_DOCDIR -# =default=> DATAROOTDIR/doc/PROJECT_NAME -# =default=> CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring -set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov) +if (XO_SUBMODULE_BUILD) + # in submodule build, generate aggregate coverage report + # for all xo libraries +else() + set(CCOV_OUTPUT_DIR ${PROJECT_BINARY_DIR}/ccov/html) + set(CCOV_INDEX_FILE ${CCOV_OUTPUT_DIR}/index.html) + set(CCOV_REPORT_EXE ${PROJECT_BINARY_DIR}/gen-ccov) + # CMAKE_INSTALL_DOCDIR + # =default=> DATAROOTDIR/doc/PROJECT_NAME + # =default=> CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring + set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov) -# 'test' target should always be out-of-date -# -# DEPENDS: reminder - can't put 'test' here, requires 'all' target -# -add_custom_command( - OUTPUT ${CCOV_INDEX_FILE} - DEPENDS ${SELF_EXE} - COMMAND ${CCOV_REPORT_EXE} - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - COMMENT "Generating coverage report -> [${CCOV_OUTPUT_DIR}]") + # 'test' target should always be out-of-date + # + # DEPENDS: reminder - can't put 'test' here, requires 'all' target + # + add_custom_command( + OUTPUT ${CCOV_INDEX_FILE} + DEPENDS ${SELF_EXE} + COMMAND ${CCOV_REPORT_EXE} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Generating coverage report -> [${CCOV_OUTPUT_DIR}]") -add_custom_target( - ccov - DEPENDS ${CCOV_INDEX_FILE} ${SELF_EXE}) + add_custom_target( + ccov + DEPENDS ${CCOV_INDEX_FILE} ${SELF_EXE}) -# OPTIONAL: quietly skip this step if ccov report not generated -install( - DIRECTORY ${CCOV_OUTPUT_DIR} - FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ - DESTINATION ${CCOV_INSTALL_DOCDIR} - COMPONENT Documentation - OPTIONAL) + # OPTIONAL: quietly skip this step if ccov report not generated + install( + DIRECTORY ${CCOV_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CCOV_INSTALL_DOCDIR} + COMPONENT Documentation + OPTIONAL) +endif() # ---------------------------------------------------------------- # deps: logutils, ... From 5cd6f2c71a8f9fdf47acfceca6c4ef570b4910df Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 13:29:19 -0400 Subject: [PATCH 0641/2524] xo-flatstring: minor comment-only --- utest/flatstring_utest_main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utest/flatstring_utest_main.cpp b/utest/flatstring_utest_main.cpp index e7a2c140..e2721d06 100644 --- a/utest/flatstring_utest_main.cpp +++ b/utest/flatstring_utest_main.cpp @@ -1,6 +1,6 @@ -/* @file flatstring_utest_main.cpp */ +/** @file flatstring_utest_main.cpp **/ #define CATCH_CONFIG_MAIN #include "catch2/catch.hpp" -/* end flatstring_utest_main.cpp */ +/** end flatstring_utest_main.cpp **/ From 87c64c59e01ce89eea90256d9119fea36acab0ad Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 13:29:43 -0400 Subject: [PATCH 0642/2524] xo-flatstring: drop editor tmp from repo --- utest/.#flatstring_utest_main.cpp | 1 - 1 file changed, 1 deletion(-) delete mode 120000 utest/.#flatstring_utest_main.cpp diff --git a/utest/.#flatstring_utest_main.cpp b/utest/.#flatstring_utest_main.cpp deleted file mode 120000 index a0c5445f..00000000 --- a/utest/.#flatstring_utest_main.cpp +++ /dev/null @@ -1 +0,0 @@ -roland@roly-desktop-23.717424:1712774400 \ No newline at end of file From 787e19ae409a0bec2ee6e3bd29ac5366b3dfb28a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:05:45 -0400 Subject: [PATCH 0643/2524] xo-ratio: mark several methods noexcept --- include/xo/ratio/ratio.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/xo/ratio/ratio.hpp b/include/xo/ratio/ratio.hpp index 60aad611..a7848518 100644 --- a/include/xo/ratio/ratio.hpp +++ b/include/xo/ratio/ratio.hpp @@ -147,10 +147,10 @@ namespace xo { return compare_aux(x, y); } - constexpr Int num() const { return num_; } - constexpr Int den() const { return den_; } + constexpr Int num() const noexcept { return num_; } + constexpr Int den() const noexcept { return den_; } - constexpr bool is_integer() const { return den_ == 1 || den_ == -1; } + constexpr bool is_integer() const noexcept { return den_ == 1 || den_ == -1; } constexpr ratio negate() const { return ratio(-num_, den_); } constexpr ratio reciprocal() const { return ratio(den_, num_); } @@ -200,7 +200,7 @@ namespace xo { /** @brief convert to representation using different integer types **/ template - constexpr operator Ratio2 () const requires ratio_concept { + constexpr operator Ratio2 () const noexcept requires ratio_concept { return Ratio2(num_, den_); } @@ -209,7 +209,7 @@ namespace xo { * * @pre @p x, @p y have non-negative denominator **/ - static constexpr auto compare_aux(ratio x, ratio y) { + static constexpr auto compare_aux(ratio x, ratio y) noexcept { /* control here: b>=0, d>=0 */ /* (a/b) <=> (c/d) From c0b2e832116ecc09df6fb5ffe4fe14c5b6086128 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:06:29 -0400 Subject: [PATCH 0644/2524] xo-ratio: + .to_str() method using xo-flatstring --- CMakeLists.txt | 1 + cmake/xo_ratioConfig.cmake.in | 2 +- include/xo/ratio/ratio.hpp | 38 ++++++++++++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 077f1267..ca9e8c95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- # dependencies +xo_headeronly_dependency(${SELF_LIB} xo_flatstring) #xo_headeronly_dependency(${SELF_LIB} randomgen) # etc.. diff --git a/cmake/xo_ratioConfig.cmake.in b/cmake/xo_ratioConfig.cmake.in index e5ee1778..b7a5a0a2 100644 --- a/cmake/xo_ratioConfig.cmake.in +++ b/cmake/xo_ratioConfig.cmake.in @@ -6,7 +6,7 @@ include(CMakeFindDependencyMacro) # must coordinate with xo_dependency() calls # in xo-reactor/src/reactor/CMakeLists.txt # -#find_dependency(reflect) +find_dependency(xo_flatstring) #find_dependency(subsys) #find_dependency(Eigen3) #find_dependency(webutil) diff --git a/include/xo/ratio/ratio.hpp b/include/xo/ratio/ratio.hpp index a7848518..a8f09e58 100644 --- a/include/xo/ratio/ratio.hpp +++ b/include/xo/ratio/ratio.hpp @@ -6,6 +6,7 @@ #pragma once #include "ratio_concept.hpp" +#include "xo/flatstring/flatstring.hpp" #include #include //#include @@ -196,7 +197,42 @@ namespace xo { * For example: to int or double **/ template - constexpr Repr to() const { return num_ / static_cast(den_); } + constexpr Repr to() const noexcept { return num_ / static_cast(den_); } + + /** @brief convert to short human-friendly flatstring representation + * + * Example: + * @code + * ratio(7,1).to_str<5>(); // "7" + * ratio(1,7).to_str<5>(); // "(1/7)" + * ratio(-1,7).to_str<10>(); // "(-1/7)" + * ratio(-1,7).to_str<5>(); // "(-1/" + * @endcode + **/ + template + constexpr flatstring to_str() const noexcept { + if (this->is_integer()) { + return flatstring::from_int(num_); + } else { + auto num_str = flatstring::from_int(num_); + auto den_str = flatstring::from_int(den_); + + /* tmp capacity will be about 2N+3 */ + auto tmp = flatstring_concat(flatstring("("), + num_str, + flatstring("/"), + den_str, + flatstring(")")); + + flatstring retval; + retval.assign(tmp); + + return retval; + } + } + + /** @brief negate operator **/ + constexpr ratio operator-() const { return ratio(-num_, den_); } /** @brief convert to representation using different integer types **/ template From d907f4eaff9d8cd1095d171b13ac34fde58fcaba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:07:01 -0400 Subject: [PATCH 0645/2524] xo-ratio: + unit test --- utest/CMakeLists.txt | 57 +++++++++ utest/ratio.test.cpp | 235 +++++++++++++++++++++++++++++++++++++ utest/ratio_utest_main.cpp | 6 + 3 files changed, 298 insertions(+) create mode 100644 utest/CMakeLists.txt create mode 100644 utest/ratio.test.cpp create mode 100644 utest/ratio_utest_main.cpp diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..2da04264 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,57 @@ +# xo-ratio/utest/CMakeLists.txt + +set(SELF_EXE utest.ratio) +set(SELF_SRCS + ratio_utest_main.cpp + ratio.test.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) +add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) + +# ---------------------------------------------------------------- +# in coverage build, target to build+install coverage report + +if (XO_SUBMODULE_BUILD) + # in submodule build, generate aggregate coverage report + # for all xo libraries +else() + set(CCOV_OUTPUT_DIR ${PROJECT_BINARY_DIR}/ccov/html) + set(CCOV_INDEX_FILE ${CCOV_OUTPUT_DIR}/index.html) + set(CCOV_REPORT_EXE ${PROJECT_BINARY_DIR}/gen-ccov) + # CMAKE_INSTALL_DOCDIR + # =default=> DATAROOTDIR/doc/PROJECT_NAME + # =default=> CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring + set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov) + + # 'test' target should always be out-of-date + # + # DEPENDS: reminder - can't put 'test' here, requires 'all' target + # + add_custom_command( + OUTPUT ${CCOV_INDEX_FILE} + DEPENDS ${SELF_EXE} + COMMAND ${CCOV_REPORT_EXE} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Generating coverage report -> [${CCOV_OUTPUT_DIR}]") + + add_custom_target( + ccov + DEPENDS ${CCOV_INDEX_FILE} ${SELF_EXE}) + + # OPTIONAL: quietly skip this step if ccov report not generated + install( + DIRECTORY ${CCOV_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CCOV_INSTALL_DOCDIR} + COMPONENT Documentation + OPTIONAL) +endif() + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_headeronly_dependency(${SELF_EXE} xo_ratio) +xo_dependency(${SELF_EXE} randomgen) +xo_dependency(${SELF_EXE} indentlog) +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) diff --git a/utest/ratio.test.cpp b/utest/ratio.test.cpp new file mode 100644 index 00000000..918f9b50 --- /dev/null +++ b/utest/ratio.test.cpp @@ -0,0 +1,235 @@ +/** @file ratio.utest.cpp **/ + +#include "xo/ratio/ratio.hpp" +#include "xo/ratio/ratio_iostream.hpp" +#include "xo/randomgen/random_seed.hpp" +#include "xo/randomgen/xoshiro256.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/vector.hpp" +#include "xo/indentlog/print/array.hpp" +#include "xo/indentlog/print/tag.hpp" +//#include "xo/indentlog/print/hex.hpp" +#include +#include +#include + +namespace xo { + + using std::exponential_distribution; + using std::bernoulli_distribution; + + namespace ut { + template + struct ratio_distribution { + ratio_distribution(double sign_prob, double int_lambda) + : sign_dist_{sign_prob}, int_dist_{int_lambda} {} + + template + xo::ratio::ratio + random_ratio(Rng & rng) { + Int num_sign = sign_dist_(rng) ? -1 : +1; + Int num = num_sign * (1 + int_dist_(rng)); + Int den_sign = sign_dist_(rng) ? -1 : +1; + Int den = den_sign * (1 + int_dist_(rng)); + + return xo::ratio::ratio(num, den).reduce(); + } + + template + xo::ratio::ratio operator()(Rng & rng) { + return random_ratio(rng); + } + + /* generate negative numbers some of the time */ + bernoulli_distribution sign_dist_; + /* create ratios involving integers, but don't need integers to be too large */ + exponential_distribution int_dist_; + }; + + template + void + ratio_tests(Rng & rng) + { + constexpr bool debug_flag = true; + + std::size_t n_ratio = 25; + std::size_t n_experiment = n_ratio * n_ratio / 4; + /* want to avoid integer overflow when exponentiating */ + constexpr int max_pwr = 5; + + scope log(XO_DEBUG2(debug_flag, "ratio_tests")); + log && log(xtag("n_ratio", n_ratio)); + + ratio_distribution ratio_dist(0.25 /*sign_prob*/, + 0.05 /*lambda */); + bernoulli_distribution sign_dist(0.5); + exponential_distribution power_dist(0.2 /*lambda*/); + + std::vector> ratio_v; + + /* ensure 0, 1, -1 all present */ + ratio_v.push_back(xo::ratio::ratio(0,1)); + ratio_v.push_back(xo::ratio::ratio(1,1)); + ratio_v.push_back(xo::ratio::ratio(-1,1)); + + for (std::uint32_t i=0, n=n_ratio - ratio_v.size(); i(ratio1.den()); + double ratio2_approx = ratio2.num() / static_cast(ratio2.den()); + + { + auto sum = ratio1 + ratio2; + + double sum_approx = sum.num() / static_cast(sum.den()); + + log && log(XTAG(ratio1), XTAG(ratio2), XTAG(sum)); + + REQUIRE(sum_approx == Approx(ratio1_approx + ratio2_approx).epsilon(1e-6)); + REQUIRE(std::gcd(sum.num(), sum.den()) == 1); + REQUIRE(sum.den() > 0); + + /* comparison tests. piggyback on sum */ + { + auto cmp_approx = (sum_approx <=> ratio1_approx); + REQUIRE(cmp_approx == (sum <=> ratio1)); + } + { + bool eq = (sum == ratio1); + bool eq_approx = (sum_approx == ratio1_approx); + REQUIRE(eq == eq_approx); + } + { + bool ne = (sum != ratio1); + bool ne_approx = (sum_approx != ratio1_approx); + REQUIRE(ne == ne_approx); + } + { + bool gt = (sum > ratio1); + bool gt_approx = (sum_approx > ratio1_approx); + REQUIRE(gt == gt_approx); + } + { + bool ge = (sum >= ratio1); + bool ge_approx = (sum_approx >= ratio1_approx); + REQUIRE(ge == ge_approx); + } + { + bool lt = (sum > ratio1); + bool lt_approx = (sum_approx > ratio1_approx); + REQUIRE(lt == lt_approx); + } + { + bool le = (sum >= ratio1); + bool le_approx = (sum_approx >= ratio1_approx); + REQUIRE(le == le_approx); + } + } + + { + auto neg = -ratio1; + + double neg_approx = neg.num() / static_cast(neg.den()); + + log && log(XTAG(ratio1), XTAG(neg)); + + REQUIRE(neg_approx == Approx(-ratio1_approx).epsilon(1e-06)); + REQUIRE(std::gcd(neg.num(), neg.den()) == 1); + REQUIRE(neg.den() > 0); + } + + { + auto diff = ratio1 - ratio2; + + double diff_approx = diff.num() / static_cast(diff.den()); + + log && log(XTAG(ratio1), XTAG(ratio2), XTAG(diff)); + + REQUIRE(diff_approx == Approx(ratio1_approx - ratio2_approx).epsilon(1e-6)); + REQUIRE(std::gcd(diff.num(), diff.den()) == 1); + REQUIRE(diff.den() > 0); + } + + { + auto prod = ratio1 * ratio2; + + double prod_approx = prod.num() / static_cast(prod.den()); + + log && log(XTAG(ratio1), XTAG(ratio2), XTAG(prod)); + + REQUIRE(prod_approx == Approx(ratio1_approx * ratio2_approx).epsilon(1e-6)); + REQUIRE(std::gcd(prod.num(), prod.den()) == 1); + REQUIRE(prod.den() > 0); + } + + { + auto div = ratio1 * ratio2; + + double div_approx = div.num() / static_cast(div.den()); + + log && log(XTAG(ratio1), XTAG(ratio2), XTAG(div)); + + REQUIRE(div_approx == Approx(ratio1_approx * ratio2_approx).epsilon(1e-6)); + REQUIRE(std::gcd(div.num(), div.den()) == 1); + REQUIRE(div.den() > 0); + } + + { + int exp = (sign_dist(rng) ? -1 : +1) * power_dist(rng); + + if (std::abs(exp) >= max_pwr) { + exp = (std::signbit(exp) ? -1 : +1) * max_pwr; + } + + auto pwr = ratio1.power(exp); + + double pwr_approx = pwr.num() / static_cast(pwr.den()); + + log && log(XTAG(ratio1), XTAG(exp), XTAG(pwr)); + + REQUIRE(pwr_approx == Approx(::pow(ratio1_approx, exp)).epsilon(1e-6)); + REQUIRE(std::gcd(pwr.num(), pwr.den()) == 1); + REQUIRE(pwr.den() >= 0); + } + + { + auto ratio1_str = ratio1.template to_str<20>(); + + log && log(XTAG(ratio1_str)); + } + } + + } + + TEST_CASE("ratio", "[ratio]") { + //constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + uint64_t seed = 5521646833469436535ul; + //rng::Seed seed; + + //std::cerr << "ratio: seed=" << seed << std::endl; + + auto rng = rng::xoshiro256ss(seed); + + ratio_tests(rng); + } /*TEST_CASE(ratio)*/ + + } /*namespace ut*/ + +} /*namespace xo*/ + + +/** end ratio.utest.cpp **/ diff --git a/utest/ratio_utest_main.cpp b/utest/ratio_utest_main.cpp new file mode 100644 index 00000000..1bf5bace --- /dev/null +++ b/utest/ratio_utest_main.cpp @@ -0,0 +1,6 @@ +/** @file ratio_utest_main.cpp **/ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/** end ratio_utest_main.cpp **/ From 0c7cb8c37e18b395bf2e5aa609d01cca5b5cf19f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:07:50 -0400 Subject: [PATCH 0646/2524] xo-ratio: expand example ex1.cpp --- example/ex1/ex1.cpp | 86 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 8aa3c82d..144d3ed9 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -1,13 +1,75 @@ /** @file ex1.cpp **/ #include "xo/ratio/ratio_iostream.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/hex.hpp" #include +namespace { + using xo::xtag; + using xo::hex_view; + +#ifdef NOT_USING + template + xo::flatstring + flatstring_from_int(int x) + { + XO_SCOPE(log, always); + + constexpr size_t buf_z = 20; + + bool negative_flag = (x < 0); + std::size_t i = buf_z; + char buf[buf_z]; + std::fill_n(buf, N, '\0'); + + if (negative_flag) + x = -x; + + buf[--i] = '\0'; + + while ((i > 0) && (x != 0)) { + buf[--i] = ('0' + x % 10); + x = x / 10; + } + + if ((i > 0) && negative_flag) + buf[--i] = '-'; + + char retv[N]; + std::fill_n(retv, N, '\0'); + std::copy_n(buf + i, buf_z - i, retv); + + log && log(xtag("i",i), xtag("buf[i..]", hex_view(buf+i, buf+buf_z, true))); + + return retv; + } +#endif + + template + constexpr xo::flatstring + ratio_to_str(Ratio x) noexcept + { + if (x.is_integer()) { + return xo::flatstring::from_int(x.num()); + } else { + constexpr auto num_str = xo::flatstring::from_int(x.num()); + constexpr auto den_str = xo::flatstring::from_int(x.den()); + + constexpr auto tmp = flatstring_concat("(", num_str, "/", den_str, ")"); + + return tmp; + } + } + +} + int main() { using xo::ratio::make_ratio; using xo::ratio::ratio; using xo::ratio::ratio_concept; + using xo::flatstring; using namespace std; constexpr auto r1 = make_ratio(2, 3); @@ -158,11 +220,24 @@ main() { static_assert(!r16.is_integer()); constexpr auto r17 = 2 / r9; - cerr << "r17=2/r9: " << r17 << endl; + cerr << "r17=2/r9: " << r17 << endl; // 9/2 static_assert(r17 == ratio(9, 2)); static_assert(!r17.is_integer()); + constexpr auto s17_num_str = flatstring<20>::from_int(r17.num()); + static_assert(s17_num_str == flatstring("9")); + constexpr auto s17_den_str = flatstring<20>::from_int(r17.den()); + static_assert(s17_den_str == flatstring("2")); + constexpr auto s17_str = flatstring_concat(flatstring_concat(flatstring("("), + s17_num_str), + flatstring_concat(flatstring("/"), + s17_den_str), + flatstring(")")); + cerr << "s17_str=" << s17_str << endl; + + //constexpr auto s17 = ratio_to_str(r17); + constexpr auto r18 = r12 / r8; cerr << "r18=r12/r8: " << r12/r8 << endl; @@ -180,6 +255,15 @@ main() { static_assert(!r20.is_integer()); static_assert(r20 == ratio(8, 729)); + //cerr << flatstring_from_int<10>(1) << endl; + + constexpr auto s20 = flatstring<10>::from_int(-123); + cerr << "s20=" << s20 << endl; + + static_assert(s20.size() > 0); + static_assert(s20 == flatstring("-123")); + + /* verify constexpr working */ static_assert(ratio(2,3).num() == 2); static_assert(ratio(2,3).den() == 3); From ec73e10c56c5bd1ae0a097aafd2f708f98c1001b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:08:04 -0400 Subject: [PATCH 0647/2524] xo-ratio: build: + utest dir (!) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ca9e8c95..44a7c278 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ xo_toplevel_compile_options() # ---------------------------------------------------------------- add_subdirectory(example) -#add_subdirectory(utest) +add_subdirectory(utest) #add_subdirectory(docs) # ---------------------------------------------------------------- From 413c84d65e27fe1b17a39e4393d8bcf0d102aed4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:12:22 -0400 Subject: [PATCH 0648/2524] xo-ratio: README: ++ elaborate --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 722ea8bf..cf8b2de2 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Relative to `std::ratio`: This means it can be used seamlessly at runtime. 2. Supports a few more arithmetic operations, for example exponentiation to integer powers. +3. Provides constexpr conversion to fixed-capacity strings (using xo-flatstring) 3. Provides concept support (with c++20) 4. Requires modern (c++17) support to achieve this @@ -15,6 +16,7 @@ Relative to `std::ratio`: ### install dependencies - [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros +- [github/Rconybea/xo-flatstring](https://github.com/Rconybea/xo-flatstring) fixed-capacity strings ### build + install ``` From 96e50d0a6b5d93b4b58e6607349b8e91b8e399fc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:19:29 -0400 Subject: [PATCH 0649/2524] xo-ratio: README elaboration --- README.md | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cf8b2de2..2ed3e4e5 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ Relative to `std::ratio`: 3. Provides concept support (with c++20) 4. Requires modern (c++17) support to achieve this +Relative to `boost::ratio`: +1. Streamlined, assumes modern compiler support + ## Getting Started ### install dependencies @@ -18,11 +21,45 @@ Relative to `std::ratio`: - [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros - [github/Rconybea/xo-flatstring](https://github.com/Rconybea/xo-flatstring) fixed-capacity strings +### clone xo-ratio + +``` +$ cd ~/proj # for example +$ git clone https://github.com/Rconybea/xo-ratio +``` + ### build + install ``` $ cd xo-ratio $ PREFIX=/usr/local # for example $ BUILDDIR=.build # for example $ make ${BUILDDIR} -$ cmake -DCMAKE_PREFIX_PATH=${PREFIX} -B ${BUILDDIR} +$ cmake --build .build +$ cmake --install .build +``` + +### build with unit test coverage +``` +$ cd xo-ratio +$ mkdir .build-ccov +$ cmake -DCMAKE_BUILD_TYPE=coverage -B .build-ccov +$ cmake --build .build-ccov +``` + +run coverage-enabled unit tests +``` +$ cmake --build .build-ccov -- test +``` + +generate html+text coverage report +``` +$ cmake --build .build-ccov -- ccov +``` + +browse to `.build-ccov/ccov/html/index.html` + +### LSP support +``` +$ cd xo-ratio +$ ln -s .build/compile_commands.json ``` From 66998590dc7fc7b4c9b1b9e1418e9696506c2f80 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:24:09 -0400 Subject: [PATCH 0650/2524] xo-flatstring: ++ README --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 0e7ca89b..fa3dcc93 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,18 @@ Limitations: ## Getting started +### Install dependencies + +- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros +- [github/Rconybea/xo-indentlog](https://github.com/Rconybea/indentlog) logging (used by unit tests) + +### Clone xo-flatstring + +``` +$ cd ~/proj # for example +$ git clone https://github.com/rconybea/xo-flatstring +``` + ### build + install ``` $ cd xo-flatstring From d5747dde17fab6d0c100d558c32a8bd0532eafc3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:24:22 -0400 Subject: [PATCH 0651/2524] xo-flatstring: + from_flatstring() + from_chars() --- include/xo/flatstring/flatstring.hpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/include/xo/flatstring/flatstring.hpp b/include/xo/flatstring/flatstring.hpp index 41bfffa3..da80014b 100644 --- a/include/xo/flatstring/flatstring.hpp +++ b/include/xo/flatstring/flatstring.hpp @@ -168,6 +168,26 @@ namespace xo { } ///@} + /** @brief construct from another flatstring **/ + template + static constexpr flatstring from_flatstring(const flatstring & str) noexcept { + flatstring retval; + + retval.assign(str); + + return retval; + } + + /** @brief construct from char array **/ + template + static constexpr flatstring from_chars(const char (&str)[N2]) noexcept { + flatstring retval; + + retval.assign(str); + + return retval; + } + /** @brief construct from integer **/ static constexpr flatstring from_int(int x) { constexpr size_t buf_z = 20; From b1b8d89f7c2fe41261e5bd98897c6119a0979942 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:24:35 -0400 Subject: [PATCH 0652/2524] xo-flatstring: README: drop install detail for coverage build --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index fa3dcc93..344cead4 100644 --- a/README.md +++ b/README.md @@ -72,9 +72,6 @@ $ cmake --build .build-ccov -- ccov browse to `.build-ccov/ccov/html/index.html` -Running `cmake --install` for a coverage build will install coverage report (if generated) -to `${CMAKE_INSTALL_PREFIX}/share/doc/xo_flatstring` - ### LSP support ``` $ cd xo-flatstring From a848e5ea67ebe8713cd339b6a7cf8a6b0ab40b6a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:25:54 -0400 Subject: [PATCH 0653/2524] xo-ratio: ++ README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2ed3e4e5..5b6dda3a 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Relative to `boost::ratio`: ### install dependencies - [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros +- [github/rconybea/xo-indentlog](https://github.com/Rconybea/xo-indentlog) logging (used by unit tests) - [github/Rconybea/xo-flatstring](https://github.com/Rconybea/xo-flatstring) fixed-capacity strings ### clone xo-ratio From fd305ffe8f928cd4736a6870cdeb28738a6ac511 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:26:02 -0400 Subject: [PATCH 0654/2524] xo-ratio: github: + ubuntu build --- .github/workflows/ubuntu-main.yml | 186 ++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 .github/workflows/ubuntu-main.yml diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml new file mode 100644 index 00000000..260a8310 --- /dev/null +++ b/.github/workflows/ubuntu-main.yml @@ -0,0 +1,186 @@ +name: build xo-ratio + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + # install catch2, doxygen. see + # [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + + echo "::group::install catch2" + sudo apt-get install -y catch2 + echo "::endgroup" + + echo "::group::install doxygen" + sudo apt-get install -y doxygen + echo "::endgroup" + + echo "::group::install sphinx" + sudo apt-get install -y python3-sphinx + echo "::endgroup" + + echo "::group::install sphinx readthedocs theme" + sudo apt-get install -y python3-sphinx-rtd-theme + echo "::endgroup" + + #echo "::group::install pybind11" + #sudo apt-get install -y pybind11-dev + #echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: build xo-cmake + run: | + XONAME=xo-cmake + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/xo-indentlog + + - name: build xo-indentlog + run: | + XONAME=xo-indentlog + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-flatstring + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-flatstring + path: repo/xo-flatstring + + - name: build xo-flatstring + run: | + XONAME=xo-flatstring + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: build self (xo-ratio) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: | + XONAME=xo-ratio + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::run unit tests ${XONAME}" + cmake --build ${BUILDDIR} -- test + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + (cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}}) From cef1f81f5f32a2bf01e52308ce4463e90fa26428 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:28:18 -0400 Subject: [PATCH 0655/2524] xo-ratio: build: drop unused xo-reflect dep --- example/ex1/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt index b7050cfc..224e5690 100644 --- a/example/ex1/CMakeLists.txt +++ b/example/ex1/CMakeLists.txt @@ -10,6 +10,6 @@ xo_include_options2(${SELF_EXE}) # dependencies.. xo_self_dependency(${SELF_EXE} xo_ratio) -xo_dependency(${SELF_EXE} reflect) +#xo_dependency(${SELF_EXE} reflect) # end CMakeLists.txt From 24df33c505435a17caae2f1ee0b0b784ca29ecc8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:30:47 -0400 Subject: [PATCH 0656/2524] xo-ratio: github: + randomgen dep --- .github/workflows/ubuntu-main.yml | 35 +++++++++++++++++++++++++++++++ README.md | 3 ++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ubuntu-main.yml b/.github/workflows/ubuntu-main.yml index 260a8310..4b5f22cd 100644 --- a/.github/workflows/ubuntu-main.yml +++ b/.github/workflows/ubuntu-main.yml @@ -114,6 +114,41 @@ jobs: # ---------------------------------------------------------------- + - name: clone xo-randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/xo-randomgen + + - name: build xo-randomgen + run: | + XONAME=xo-randomgen + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + - name: clone xo-flatstring uses: actions/checkout@v3 with: diff --git a/README.md b/README.md index 5b6dda3a..a5047565 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,9 @@ Relative to `boost::ratio`: ### install dependencies - [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros -- [github/rconybea/xo-indentlog](https://github.com/Rconybea/xo-indentlog) logging (used by unit tests) - [github/Rconybea/xo-flatstring](https://github.com/Rconybea/xo-flatstring) fixed-capacity strings +- [github/rconybea/xo-indentlog](https://github.com/Rconybea/xo-indentlog) logging (used by unit tests) +- [github/rconybea/xo-randomgen](https://github.com/Rconybea/xo-randomgen) rng (used by unit tests) ### clone xo-ratio From 54cb306903e3662774d3df62d0c21ef9c292fa80 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:35:53 -0400 Subject: [PATCH 0657/2524] xo-ratio: bugfix: narrow operator<< template --- include/xo/ratio/ratio_iostream.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/xo/ratio/ratio_iostream.hpp b/include/xo/ratio/ratio_iostream.hpp index fc898b36..9d1c9939 100644 --- a/include/xo/ratio/ratio_iostream.hpp +++ b/include/xo/ratio/ratio_iostream.hpp @@ -17,9 +17,9 @@ namespace xo { * print_ratio(std::cerr, make_ratio(1,2); // outputs "" * @endcode **/ - template + template void - print_ratio (std::ostream & os, const Ratio & x) { + print_ratio (std::ostream & os, const ratio & x) { os << ""; } @@ -30,9 +30,9 @@ namespace xo { * std::cout << make_ratio(2,3); // outputs "" * @endcode **/ - template + template inline std::ostream & - operator<< (std::ostream & os, const Ratio & x) { + operator<< (std::ostream & os, const ratio & x) { print_ratio(os, x); return os; } From 75f78b45d3a8a89c46223033be75420816969119 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 17:48:40 -0400 Subject: [PATCH 0658/2524] xo-ratio: ccov: renovate coverage generation -> 2nd gen scheme --- CMakeLists.txt | 38 +++++++++++---- README.md | 5 ++ cmake/gen-ccov.in | 20 ++++++++ cmake/lcov-harness | 114 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 168 insertions(+), 9 deletions(-) create mode 100644 cmake/gen-ccov.in create mode 100755 cmake/lcov-harness diff --git a/CMakeLists.txt b/CMakeLists.txt index 44a7c278..df36a04d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,17 +12,37 @@ include(cmake/xo-bootstrap-macros.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. +# ---------------------------------------------------------------- +# cmake -DCMAKE_BUILD_TYPE=coverage + +if (NOT DEFINED PROJECT_CXX_FLAGS_COVERAGE) + # note: for clang would use -fprofile-instr-generate -fcoverage-mapping here instead and also at link time + set(PROJECT_CXX_FLAGS_COVERAGE ${PROJECT_CXX_FLAGS} -ggdb -Og -fprofile-arcs -ftest-coverage + CACHE STRING "coverage c++ compiler flags") +endif() +message("-- PROJECT_CXX_FLAGS_COVERAGE: coverage c++ flags are [${PROJECT_CXX_FLAGS_COVERAGE}]") + +add_compile_options("$<$:${PROJECT_CXX_FLAGS_COVERAGE}>") +# when -DCMAKE_BUILD_TYPE=coverage, link executables with gcov +link_libraries("$<$:gcov>") + +find_program(LCOV_EXECUTABLE NAMES lcov) +find_program(GENHTML_EXECUTABLE NAMES genhtml) + +# with coverage build: +# 1. invoke instrumented executables for which you want coverage: +# (cd path/to/build && ctest) +# 2. post-process low-level coverage data +# (path/to/build/gen-ccov) +# 3. point browser to generated html data +# file:///path/to/build/ccov/html/index.html # -# 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/*) +configure_file( + ${PROJECT_SOURCE_DIR}/cmake/gen-ccov.in + ${PROJECT_BINARY_DIR}/gen-ccov) + +file(CHMOD ${PROJECT_BINARY_DIR}/gen-ccov PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) # ---------------------------------------------------------------- # c++ settings diff --git a/README.md b/README.md index a5047565..09c36590 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,11 @@ Relative to `std::ratio`: Relative to `boost::ratio`: 1. Streamlined, assumes modern compiler support +## Documentation + +- xo-ratio documentation under construction +- unit test coverage here: [coverage](https://rconybea.github.io/web/xo-ratio/ccov/html/index.html) + ## Getting Started ### install dependencies diff --git a/cmake/gen-ccov.in b/cmake/gen-ccov.in new file mode 100644 index 00000000..e335aed4 --- /dev/null +++ b/cmake/gen-ccov.in @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +srcdir=@PROJECT_SOURCE_DIR@ +builddir=@PROJECT_BINARY_DIR@ +lcov=@LCOV_EXECUTABLE@ +genhtml=@GENHTML_EXECUTABLE@ + +if [[ $lcov == "LCOV_EXECUTABLE-NOTFOUND" ]]; then + echo "gen-ccov: lcov executable not found" + exit 1 +fi + +if [[ $genhtml == "GENHTML_EXECUTABLE-NOTFOUND" ]]; then + echo "gen-ccov: genhtml executable not found" + exit 1 +fi + +mkdir $builddir/ccov + +$srcdir/cmake/lcov-harness $srcdir $builddir $builddir/ccov/out $lcov $genhtml diff --git a/cmake/lcov-harness b/cmake/lcov-harness new file mode 100755 index 00000000..27ac8be9 --- /dev/null +++ b/cmake/lcov-harness @@ -0,0 +1,114 @@ +#!/usr/bin/env bash + +srcdir=$1 +builddir=$2 +outputstem=$3 +lcov=$4 +genhtml=$5 + +if [[ -z "${srcdir}" ]]; then + echo "lcov-harness: expected non-empty srcdir" + exit 1 +fi + +if [[ -z ${builddir} ]]; then + echo "lcov-harness: expected non-empty builddir" + exit 1 +fi + +if [[ -z ${outputstem} ]]; then + echo "lcov-harness: expected non-empty outputstem" + exit 1 +fi + +if [[ -z ${lcov} ]]; then + echo "lcov-harness: exepcted non-empty lcov" + exit 1 +fi + +if [[ -z ${genhtml} ]]; then + echo "lcov-harness: expected non-empty genhtml" + exit 1 +fi + +# directory stems for location of {.gcda, gcno} coverage information, +# +# if we have source tree: +# +# ${srcdir} +# +- foo +# | \- foo.cpp +# \- bar +# \- quux +# +- quux.cpp +# \- quux_main.cpp +# +# then we expect build tree: +# +# ${builddir} +# +- foo +# | \- CMakeFiles +# | \- foo_target.dir +# | +- foo.cpp.gcda +# | \- foo.cpp.gcno +# +- bar +# \- quux +# \- CMakeFiles +# \- target4quux.dir +# +- quux.cpp.gcda +# +- quux.cpp.gcno +# +- quux_main.cpp.gcda +# \- quux_main.cpp.gcno +# +# in which case will have cmd_body: +# +# ${primarydirs} +# ./foo/CMakeFiles/foo_target.dir +# ./bar/quux/CMakeFiles/target4quux.dir +# +# here foo_target, quux_target are whatever build is using for corresponding cmake target names. +# +# We want to invoke lcov like: +# +# lcov --capture \ +# --output ${builddir}/ccov \ +# --exclude /utest/ \ +# --base-directory ${srcdir}/foo --directory ${builddir}/foo/CMakeFiles/foo_target.dir \ +# --base-directory ${srcdir}/bar/quux --directory ${builddir}/bar/quux/CMakeFiles/target4quux.dir +# +primarydirs=$(cd ${builddir} && find -name '*.gcno' \ + | xargs --replace=xx dirname xx \ + | uniq \ + | sed -e 's:^\./::') + +#echo "primarydirs=${primarydirs}" + +cmd="${lcov} --output ${outputstem}.info --capture --ignore-errors source" + +for bdir in ${primarydirs}; do + sdir=$(dirname $(dirname ${bdir})) + + cmd="${cmd} --base-directory ${srcdir}/${sdir} --directory ${builddir}/${bdir}" +done + +#echo cmd=${cmd} + +set -x + +# capture +${cmd} + +# keep only files with paths under source tree +# (don't want coverage for external libraries such as libstdc++ etc) +${lcov} --extract ${outputstem}.info "${srcdir}/*" --output ${outputstem}2.info + +# remove unit test dirs +# (we're interested in coverage of our installed code, not of the unit tests that exercise it) +${lcov} --remove ${outputstem}2.info '*/utest/*' --output ${outputstem}3.info + +# generate .html tree +mkdir -p ${builddir}/ccov/html +${genhtml} --ignore-errors source --show-details --prefix ${srcdir} --output-directory ${builddir}/ccov/html ${outputstem}3.info + +# also send report to stdout +${lcov} --list ${outputstem}3.info From 06e230ebfddc606893d8342a30873ddec61e6d8c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 18 Apr 2024 18:03:11 -0400 Subject: [PATCH 0659/2524] xo-ratio: docs: initial doxygen+sphinx scaffold --- CMakeLists.txt | 2 +- docs/CMakeLists.txt | 118 ++ docs/Doxyfile.in | 2816 ++++++++++++++++++++++++++++++++++ docs/_static/img/favicon.ico | Bin 0 -> 309803 bytes docs/_static/img/icon.svg | 77 + docs/_static/img/xo-icon.svg | 77 + docs/conf.py | 36 + docs/index.rst | 34 + docs/install.rst | 88 ++ 9 files changed, 3247 insertions(+), 1 deletion(-) create mode 100644 docs/CMakeLists.txt create mode 100644 docs/Doxyfile.in create mode 100644 docs/_static/img/favicon.ico create mode 100644 docs/_static/img/icon.svg create mode 100644 docs/_static/img/xo-icon.svg create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/install.rst diff --git a/CMakeLists.txt b/CMakeLists.txt index df36a04d..882e9914 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,7 @@ xo_toplevel_compile_options() add_subdirectory(example) add_subdirectory(utest) -#add_subdirectory(docs) +add_subdirectory(docs) # ---------------------------------------------------------------- # provide find_package() support for projects using this library diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 00000000..57a99c20 --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,118 @@ +# xo-ratio/docs/CMakeLists.txt + +if (XO_SUBMODULE_BUILD) + # in submodule build, rely on toplevel docs/CMakeLists.txt file instead +else() + # build docs starting from here only in standalone build. + # otherwise use top-level doxygen setup instead. + + set(ALL_LIBRARY_TARGETS xo_ratio) # todo: automate this from xo-cmake macros + set(ALL_UTEST_TARGETS utest.ratio xo_ratio_ex1 ) # todo: automate this from xo-cmake macros + + # look for doxygen executable + find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED) + message("-- DOXYGEN_EXECUTABLE=${DOXYGEN_EXECUTABLE}") + + # look for sphinx-build executable + find_program(SPHINX_EXECUTABLE NAMES sphinx-build REQUIRED) + message("-- SPHINX_EXECUTABLE=${SPHINX_EXECUTABLE}") + + set(DOX_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + + set(DOX_INPUT_DIR ${PROJECT_SOURCE_DIR}) + set(DOX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dox) + + set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html) + + # .hpp files reachable from xo-ratio/include + # + # REMINDER: for reliability will need to re-run cmake when the set of .hpp files changes + # + file(GLOB_RECURSE DOX_HPP_FILES_GLOB ${PROJECT_SOURCE_DIR}/include *.hpp) + + set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/sphinx/html) + set(SPHINX_INDEX_FILE ${SPHINX_OUTPUT_DIR}/index.html) + # + # sphinx .rst files reachable from cmake-examples/docs + # + # REMINDER: for reliability will need to re-run cmake when the set of .rst files changes + # + file(GLOB_RECURSE SPHINX_RST_FILES_GLOB ${CMAKE_CURRENT_SOURCE_DIR} *.rst) + + set(SPHINX_RST_FILES index.rst + #install.rst + #lessons.rst + #flatstring-reference.rst + #flatstring-class.rst + ) + + # TODO: + # 1. move Doxyfile.in to xo-cmake project + # 2. replace this command section with xo-cmake macro + # + configure_file( + Doxyfile.in ${DOX_CONFIG_FILE} + FILE_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE + @ONLY) + + set(DOX_DEPS ${ALL_LIBRARY_TARGETS} ${ALL_UTEST_TARGETS} ${DOX_HPP_FILES_GLOB}) + + file(MAKE_DIRECTORY ${DOX_OUTPUT_DIR}) + add_custom_command( + OUTPUT ${DOX_INDEX_FILE} + DEPENDS ${DOX_DEPS} + COMMAND "${DOXYGEN_EXECUTABLE}" ${DOX_CONFIG_FILE} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + MAIN_DEPENDENCY ${DOX_CONFIG_FILE} + COMMENT "Generating docs (doxygen)") + + # To build this target + # $ cmake --build .build -j -- doxygen + # or + # $ cd .build + # $ make doxygen + # + add_custom_target( + doxygen + DEPENDS ${DOX_INDEX_FILE} ${DOX_DEPS} + ) + + # root of sphinx doc tree + set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) + set(SPHINX_DEPS doxygen conf.py ${SPHINX_RST_FILES} ${SPHINX_RST_FILES_GLOB} ${DOX_DEPS}) + + add_custom_command( + OUTPUT ${SPHINX_INDEX_FILE} + DEPENDS ${SPHINX_DEPS} + COMMAND ${SPHINX_EXECUTABLE} + -b html -Dbreathe_projects.xodoxxml=${CMAKE_CURRENT_BINARY_DIR}/dox/xml + ${SPHINX_SOURCE} ${SPHINX_OUTPUT_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating docs (sphinx) -> [${SPHINX_OUTPUT_DIR}]") + + # make sphinx --> generate sphinx documentation + # + add_custom_target( + sphinx + DEPENDS ${SPHINX_INDEX_FILE}) + + # - html docs generated in build/docs/sphinx + # - copy the doc tree to share/doc/xo_unit/html + # + # DESTINATION: CMAKE_INSTALL_DOCDIR + # => DATAROOTDIR/doc/PROJECT_NAME + # => CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring + # OPTIONAL: install directory tree if it exists, + # but don't complain if it's missing + install( + DIRECTORY ${SPHINX_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_DOCDIR} + COMPONENT Documentation + OPTIONAL) + + # make docs --> generate sphinx documentation + add_custom_target( + docs + DEPENDS sphinx) +endif() diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in new file mode 100644 index 00000000..13dfb0fa --- /dev/null +++ b/docs/Doxyfile.in @@ -0,0 +1,2816 @@ +# If filename is Doxyfile.in: +# template (to be expanded by cmake) for real doxyfile +# @SOMEVAR@ expands to value of cmake variable SOMEVAR +# +# expressions to be expanded include: +# @DOX_INPUT_DIR@ +# @DOX_OUTPUT_DIR@ +# +# if filename is Doxyfile: +# expanded template in build directory, to configure doxygen + +# Doxyfile 1.9.7 +# + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Cmake Examples" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @DOX_OUTPUT_DIR@ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = @DOX_INPUT_DIR@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN Use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0. and GITHUB Use the lower case version of title +# with any whitespace replaced by '-' and punctations characters removed.. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = YES + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = YES + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = YES + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = SYSTEM + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @DOX_INPUT_DIR@ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = */utest/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /

); - static_assert(bpu_list_concept); - - /** For example: - * using b1 = basis_power_unit>; - * using b2 = basis_power_unit>; - * using foo = dimension_impl>; - * - * then - * foo::lookup_bpu<0> -> b1 - * foo::lookup_bpu<1> -> b2 - * foo::lookup_bpu<2> -> not defined - **/ - using front_type = P; - using rest_type = D; - - static constexpr std::uint32_t n_dimension = rest_type::n_dimension + 1; - }; - - /** @class dimension - - @brief represent a composite dimension - **/ - template - struct bpu_node { - static_assert(native_bpu_concept); - static_assert(bpu_list_concept); - - using front_type = P0; - using rest_type = void; - - /** For example: - * using b1 = basis_power_unit>; - * using foo = dimension_impl; - * then - * foo::lookup_bpu<0> --> b1 - * foo::lookup_bpu<1> --> not defined - **/ - - /** number of dimensions represented by this struct **/ - static constexpr std::uint32_t n_dimension = 1; - }; - - // ----- di_replace_basis_scale ----- - - /** - * @brief Replace BpuList member with matching BasisDim, preserving everything except (inner) scalefactor - **/ - template - struct di_replace_basis_scale; - - template - struct di_replace_basis_scale_aux; - - /** specialization for non-empty BpuList **/ - template - struct di_replace_basis_scale { - using type = di_replace_basis_scale_aux::type; - }; - - /** specialization for empty BpuList -- error not found **/ - template - struct di_replace_basis_scale {}; - - /** specialization for matching front **/ - template - struct di_replace_basis_scale_aux { - using _replace_bpu_type = bpu; - - static_assert(native_bpu_concept<_replace_bpu_type>); - - /* NewBpu replaces Front */ - using type = bpu_node<_replace_bpu_type, Rest>; - }; - - template - struct di_replace_basis_scale_aux { - /* keep Front, replace NewBpu in rest */ - using type = bpu_node::type>; - }; - - // ----- bpu_cartesian_product ----- - - /** Require: - * - B isa native_bpu type - * - DI_Front is a native_bpu type - * - DI_Rest is a dimension_impl type - * - * Promise: - * - type isa dimension_impl type - **/ - template < typename B, - typename DI_Front, - typename DI_Rest, - bool MatchesFront = (B::c_native_dim == DI_Front::c_native_dim) > - struct bpu_cartesian_product_helper; - - /** require: - * - B isa native_bpu type - * - DI isa (bpu_list | void) type - **/ - template < typename B, typename DI > - struct bpu_cartesian_product { - static_assert(native_bpu_concept); - static_assert(bpu_list_concept); - - using _tmp = bpu_cartesian_product_helper; - - using outer_scalefactor_type = typename _tmp::outer_scalefactor_type; - static constexpr double c_outer_scalefactor_inexact = _tmp::c_outer_scalefactor_inexact; - - using bpu_list_type = typename _tmp::bpu_list_type; - - static_assert(ratio_concept); - static_assert(bpu_list_concept); - }; - - /** Reminder: void represents the 'no dimension' of a dimensionless quantity **/ - template < typename B > - struct bpu_cartesian_product { - using outer_scalefactor_type = std::ratio<1>; - static constexpr double c_outer_scalefactor_inexact = 1.0; - - using bpu_list_type = bpu_node; - - static_assert(ratio_concept); - static_assert(bpu_list_concept); - }; - - /* specialize for matching front */ - template - struct bpu_cartesian_product_helper { - static_assert(native_bpu_concept); - static_assert(native_bpu_concept); - static_assert(bpu_list_concept); - - /* _mult_type may have zero exponent (power_type); - * in that case bpu_smart_cons will collapse to DI_Rest - */ - using _front_mult_type = bpu_product; - - using _front_type = typename _front_mult_type::native_bpu_type; - using _rest_type = DI_Rest; - - using outer_scalefactor_type = typename _front_mult_type::outer_scalefactor_type; - static constexpr double c_outer_scalefactor_inexact = _front_mult_type::c_outer_scalefactor_inexact; - - using bpu_list_type = bpu_smart_cons_t<_front_type, DI_Rest>; - - static_assert(ratio_concept); - static_assert(bpu_list_concept); - }; - - /* specialize for not-matching-front */ - template - struct bpu_cartesian_product_helper { - static_assert(native_bpu_concept); - static_assert(native_bpu_concept); - static_assert(bpu_list_concept); - - using _rest_mult_type = bpu_cartesian_product< B, DI_Rest >; - - using _front_type = DI_Front; - using _rest_type = typename _rest_mult_type::bpu_list_type; - - using outer_scalefactor_type = typename _rest_mult_type::outer_scalefactor_type; - static constexpr double c_outer_scalefactor_inexact = _rest_mult_type::c_outer_scalefactor_inexact; - - using bpu_list_type = bpu_node; - - static_assert(ratio_concept); - static_assert(bpu_list_concept); - }; - - // ----- di_cartesian_product ----- - - template < typename D1, typename D2 > struct di_cartesian_product; - - // ----- bpu_cartesian_product1 ----- - - template < typename B1, typename R1, typename D2 > - struct di_cartesian_product1 { - static_assert(native_bpu_concept); - static_assert(bpu_list_concept); - static_assert(bpu_list_concept); - - using _tmp1_mult_type = bpu_cartesian_product; - using _tmp1_scalefactor_type = _tmp1_mult_type::outer_scalefactor_type; - using _tmp1_bpu_list_type = _tmp1_mult_type::bpu_list_type; - - using _tmp2_mult_type = di_cartesian_product; - using _tmp2_scalefactor_type = _tmp2_mult_type::outer_scalefactor_type; - using _tmp2_bpu_list_type = _tmp2_mult_type::bpu_list_type; - - using outer_scalefactor_type = std::ratio_multiply< - _tmp1_scalefactor_type, - _tmp2_scalefactor_type>; - static constexpr double c_outer_scalefactor_inexact = (_tmp1_mult_type::c_outer_scalefactor_inexact - * _tmp2_mult_type::c_outer_scalefactor_inexact); - - using bpu_list_type = _tmp2_bpu_list_type; - - static_assert(ratio_concept); - static_assert(bpu_list_concept); - }; - - template < typename B1, typename D2 > - struct di_cartesian_product1 { - static_assert(native_bpu_concept); - static_assert(bpu_list_concept); - - using _tmp_mult_type = bpu_cartesian_product; - - using outer_scalefactor_type = _tmp_mult_type::outer_scalefactor_type; - static constexpr double c_outer_scalefactor_inexact = _tmp_mult_type::c_outer_scalefactor_inexact; - - using bpu_list_type = _tmp_mult_type::bpu_list_type; - }; - - // ----- di_invert ----- - - /* note: rescaling never required here, - * since not combining basis dimensions. - */ - template - struct di_invert; - - template <> - struct di_invert { - using type = void; - }; - - template - struct di_invert { - using type = bpu_node< - typename bpu_invert::type, - typename di_invert::type - >; - }; - - // ----- di_cartesian_product ----- - - template < typename D1, typename D2 > - struct di_cartesian_product { - static_assert(bpu_list_concept); - static_assert(bpu_list_concept); - - using _mult_type = di_cartesian_product1< - typename D1::front_type, - typename D1::rest_type, - D2>; - - using outer_scalefactor_type = _mult_type::outer_scalefactor_type; - static constexpr double c_outer_scalefactor_inexact = _mult_type::c_outer_scalefactor_inexact; - - using bpu_list_type = _mult_type::bpu_list_type; - - static_assert(ratio_concept); - static_assert(bpu_list_concept); - }; - - template < typename D2 > - struct di_cartesian_product< void, D2 > { - static_assert(bpu_list_concept); - - using outer_scalefactor_type = std::ratio<1>; - static constexpr double c_outer_scalefactor_inexact = 1.0; - using bpu_list_type = D2; - }; - - template - struct di_cartesian_product< D1, void > { - static_assert(bpu_list_concept); - - using outer_scalefactor_type = std::ratio<1>; - static constexpr double c_outer_scalefactor_inexact = 1.0; - using bpu_list_type = D1; - }; - - // ----- di_assemble_abbrev ----- - - /* reminder: can't partially specialize a template function -> need struct wrapper */ - template < typename DI > - struct di_assemble_abbrev; - - /** Expect: - * - P isa native_bpu type - * - P::power_type = std::ratio<..> - * - P::c_native_dim :: dim - * - P::c_num :: int - * - P::c_den :: int - * - D isa dimension_impl type - * - D::front_type = native_bpu<..> - * - D::rest_type = dimension_impl<..> - * - D::n_dimension :: int - **/ - template - struct di_assemble_abbrev_helper { - static_assert(native_bpu_concept

); - static_assert(bpu_list_concept); - - static constexpr auto _prefix = bpu_assemble_abbrev

(); - static constexpr auto _suffix = di_assemble_abbrev::value; - - static constexpr auto value = stringliteral_concat(_prefix.value_, - ".", - _suffix.value_); - }; - - template - struct di_assemble_abbrev_helper { - static constexpr auto value = bpu_assemble_abbrev

(); - }; - - template < typename DI > - struct di_assemble_abbrev { - static_assert(bpu_list_concept); - - using _helper_type = di_assemble_abbrev_helper ; - - static constexpr auto value = _helper_type::value; - }; - - template <> - struct di_assemble_abbrev { - static constexpr auto value = stringliteral(""); - }; - - // ----- canonical_impl ----- - - template - struct canonical_impl { - /* - * bwp_front::c_index - * bwp_front::c_native_dim - */ - using _bwp_front = native_lo_bwp_of::bwp_type; - - using _front_type = typename lookup_bpu::power_unit_type; - using _rest0_type = typename without_elt::dim_type; - using _rest_type = canonical_impl<_rest0_type>::dim_type; - - using dim_type = bpu_node<_front_type, _rest_type>; - }; - - /** compute canonical renumbering of a dimension - **/ - template <> - struct canonical_impl { - using dim_type = void; - }; - - template - using canonical_t = canonical_impl::dim_type; - - } /*namespace unit*/ -} /*namespace xo*/ - -/* end dimension_impl.hpp */ diff --git a/include/xo/unit/mpl/native_bpu.hpp b/include/xo/unit/mpl/native_bpu.hpp deleted file mode 100644 index 44e1a54d..00000000 --- a/include/xo/unit/mpl/native_bpu.hpp +++ /dev/null @@ -1,259 +0,0 @@ -/* @file native_bpu.hpp */ - -#pragma once - -#include "native_bpu_concept.hpp" -#include "basis_unit.hpp" -#include - -namespace xo { - namespace unit { - // ----- native_bpu ----- - - /** @class native_bpu - - @brief represent product of a compile-time scale-factor with a rational power of a native unit - - Example: - native_bpu, ratio<-1,2>> represents unit of 1/sqrt(t) - **/ - template< - dim DimId, - typename InnerScale, - typename Power = std::ratio<1> > - struct bpu : basis_unit, InnerScale> { - static_assert(ratio_concept); - - /* native_unit provides - * - scalefactor_type --> std::ratio - * - c_native_dim :: dim - * - c_native_unit :: native_unit - */ - - using power_type = Power; - - static const int c_num = Power::num; - static const int c_den = Power::den; - }; - - /** @class bpu_assemble_abbrev - * - * @brief generate abbreviation literal. - * - * Abbreviation literal ignores outer scale factor; - * (outer scale factor should be multiplied by run-time scale when printing a quantity) - * - * Separate template from native_bpu so that abbrev can independently be specialized - **/ - template < dim dim_id, - typename InnerScale, - typename Power = std::ratio<1> > - constexpr auto bpu_assemble_abbrev_helper() { - static_assert(ratio_concept); - - return stringliteral_concat(units::scaled_native_unit_abbrev_v.value_, - stringliteral_from_exponent().value_); - }; - - /** Expect: - * - BPU is a native_bpu type: - * - BPU::scalefactor_type = std::ratio<..> - * - BPU::c_native_dim :: dim - * - BPU::power_type = std::ratio<..> - * - BPU::c_num :: int - * - BPU::c_den :: int - **/ - template < typename BPU > - constexpr auto bpu_assemble_abbrev() { - static_assert(native_bpu_concept); - - return bpu_assemble_abbrev_helper< BPU::c_native_dim, - typename BPU::scalefactor_type, - typename BPU::power_type >(); - }; - - // ----- bpu_rescale ----- - - /** - * Part I - * ------ - * We have B satisfying native_bpu_concept: - * B represents a basis-power-unit - * p - * (b.u) - * - * with - * b = B::scalefactor_type, e.g. 60 for a 1-minute unit - * u = B::dim, e.g. 1second for time - * p = B::power_type - * - * We want to construct something with similar form: - * - * p - * a'.(b'.u) - * - * representing the same dimensioned unit, - * i.e. - * p p' - * (b.u) = a'.(b'.u) - * - * with NewInnerScale -> b' - * - * p p p p - * (b.u) = (b/b') . (b'.u) = a'.(b'.u) - * - * p - * with a' = (b/b') - * - * For example: if we have B(b=60,u=time,p=2), NewInnerScale=1: - * then we want a'=3600, B'(b=1,u=time,p=2) - * - * Result represented with - * bpu_rescale::outer_scalefactor_type -> 'a - * bpu_rescale::native_bpu_type -> B' - * - * Part II - * ------- - * Want ability to rescale when p is a non-integer rational. - * In that case a' = (b/b')^p won't in general be exactly-representable, - * so we are forced to accept some loss of precision. - * - * Want to write: - * p as p' + q' with: - * p' = integer part of p - * q' = fractional part of p - * Then we can write - * a' as c'.d' with: - * c' = (b/b')^p' [exactly represented] - * d' = (b/b')^q' [floating point] - **/ - template - struct bpu_rescale { - static_assert(native_bpu_concept); - static_assert(ratio_concept); - - /* TODO: - * - native_unit::c_scale -> std::ratio, call it c_inner_scalefactor - * - ++ native_bpu::c_outer_scalefactor, will be a std::ratio - */ - - /* b/b' */ - using _t1_type = std::ratio_divide - < typename B::scalefactor_type, NewInnerScale >; - - /* p' */ - using p1_type = ratio_floor_t; - /* q' */ - using q1_type = ratio_frac_t; - - /** require p must be integral **/ - static_assert(p1_type::den == 1); - - /* note: constexpr from c++26, but already present in earlier gcc */ - static constexpr double c_outer_scalefactor_inexact = ::pow(from_ratio(), - from_ratio()); - - /** p - * a' = (b/b') - **/ - using outer_scalefactor_type = ratio_power_t< _t1_type, p1_type::num >; - - /** - * p - * (b'.u) - **/ - using native_bpu_type = bpu < B::c_native_dim, - NewInnerScale, - typename B::power_type >; - }; - - // ----- bpu_invert ----- - - /** invert a native bpu: create type for space 1/B **/ - template - struct bpu_invert { - using type = bpu < - B::c_native_dim, - typename B::scalefactor_type, - std::ratio_multiply, typename B::power_type> - >; - }; - - // ----- bpu_product ----- - - /** Suppose we have two native_bpu's {B1, B2} that scale the same native basis unit u. - * B1,B2 may be using different units {b1,b2} for u - * - * p1 - * B1 (b1, u, p1) = (b1.u) - * - * p2 - * B2 (b2, u, p2) = (b2.u) - * - * we want a representation in similar form: - * - * p' - * a' . B' (b', u, p') = a'.(b'.u) - * - * for the product (B1 x B2), i.e. - * - * p' p1 p2 - * a'.(b'.u) = (b1.u) (b2.u) - * - * We can use bpu_rescale to rewrite B2 in the form - * - * p2 - * B2' = (c'.d').(b1.u) - * - * where c' is exact, d' is inexact. - * (note however d' will be exactly 1.0 whenever p2 is integral) - * - * so we have - * - * p1 p2 - * (B1 x B2) = (b1.u) (c'.d').(b1.u) - * - * p1+p2 - * = (c'.d').(b1.u) - * - **/ - template < typename B1, typename B2 > - struct bpu_product { - static_assert(native_bpu_concept); - static_assert(native_bpu_concept); - static_assert(B1::c_native_dim == B2::c_native_dim); - - /* c'.d'.B2' = c'.d'.(b1.u)^p2 - * - * _b2p_rescaled_type::native_bpu_type -> B2' (b1, u, p2) [same basis scalefactor as B1] - * _b2p_rescaled_type::outer_scalefactor_type -> c' [exact factor] - * _b2p_rescaled_type::c_outer_scalefactor_type -> d' [inexact factor, from fractional powers] - */ - using _b2p_rescaled_type = bpu_rescale; - /* (b1.u)^p2 */ - using _b2p_sf_bpu_type = _b2p_rescaled_type::native_bpu_type; - - /* p1+p2 */ - using _p_type = std::ratio_add< - typename B1::power_type, - typename B2::power_type - >; - - /* c' */ - using outer_scalefactor_type = _b2p_rescaled_type::outer_scalefactor_type; - /* d' */ - static constexpr double c_outer_scalefactor_inexact = _b2p_rescaled_type::c_outer_scalefactor_inexact; - - /* (b1.u)^(p1+p2) */ - using native_bpu_type = bpu < - B1::c_native_dim, - typename B1::scalefactor_type, - _p_type /*Power*/ >; - }; - - } /*namespace unit*/ -} /*namespace xo*/ - -/* end native_bpu.hpp */ diff --git a/include/xo/unit/mpl/native_bpu_concept.hpp b/include/xo/unit/mpl/native_bpu_concept.hpp deleted file mode 100644 index f72c60ca..00000000 --- a/include/xo/unit/mpl/native_bpu_concept.hpp +++ /dev/null @@ -1,41 +0,0 @@ -/* @file native_bpu_concept.hpp */ - -#pragma once - -#include "ratio_concept.hpp" -#include "dim_util.hpp" -#include - -namespace xo { - namespace unit { - /** - * e.g. see native_bpu> - * - * bpu short for 'basis power unit'. - * - * NOTE: in typical c++ use, there won't be a reason to declare - * a variable of type NativeBpu. Instead will appear - * as a template argument. - **/ - template - concept native_bpu_concept = requires(NativeBpu bpu) - { - typename NativeBpu::scalefactor_type; - typename NativeBpu::power_type; - - // NativeBpu::c_native_dim :: native_dim_id - // NativeBpu::c_scale :: std::intmax_t - // NativeBpu::num :: int - // NativeBpu::den :: int - } - && ((std::is_same_v) - && ratio_concept - && ratio_concept - && (std::is_signed_v) - && (std::is_signed_v)) - // && std::copyable - ; - } /*namespace unit*/ -} /*namespace xo*/ - -/* end native_bpu_concept.hpp */ diff --git a/include/xo/unit/mpl/numeric_concept.hpp b/include/xo/unit/mpl/numeric_concept.hpp deleted file mode 100644 index f1d32f58..00000000 --- a/include/xo/unit/mpl/numeric_concept.hpp +++ /dev/null @@ -1,34 +0,0 @@ -/* @file numeric_concept.hpp */ - -#pragma once - -#include - -namespace xo { - namespace unit { - /** @concept numeric_concept - * @brief Concept for values that participate in arithmetic operations (+,-,*,/) and comparisons - * - * Intended to include at least: - * - built-in integral and floating-point types - * - xo::raio - * - xo::unit::quantity - * - * Intend numeric_concept to apply to types suitable for - * xo::unit::quantity::repr_type. - **/ - template - concept numeric_concept = requires(T x, U y) - { - { -x }; - { x - y }; - { x + y }; - { x * y }; - { x / y }; - { x == y }; - { x != y }; - }; - } /*namespace unit*/ -} /*namespace xo*/ - -/* end numeric_concept.hpp */ diff --git a/include/xo/unit/mpl/quantity.hpp b/include/xo/unit/mpl/quantity.hpp deleted file mode 100644 index c58ba080..00000000 --- a/include/xo/unit/mpl/quantity.hpp +++ /dev/null @@ -1,831 +0,0 @@ -/* @file quantity.hpp */ - -#pragma once - -#include "quantity_concept.hpp" -#include "unit.hpp" -#include "detail/promoter.hpp" -//#include "xo/reflect/Reflect.hpp" -//#include "xo/indentlog/scope.hpp" - -namespace xo { - namespace unit { - // ----- quantity ----- - - /** @class quantity - * - * @brief represents a scalar quantity; enforces dimensional consistency at compile time. - * - * - @p Unit is a type identifying dimension and scale attaching to this quantity. - * Unit must satisfy @c unit_concept - * - @p Repr is a type used to represent quantity values, scaled by @p Unit. - * Repr must satisfy @c numeric_concept - * - * A quantity's run-time state consists of exactly one @p Repr - * instance: - * @c sizeof(quantity) == sizeof(Repr) - **/ - template - class quantity { - public: - /** @defgroup mpl-quantity-traits **/ - ///@{ - /** @brief type capturing the units (and dimension) of this quantity **/ - using unit_type = Unit; - /** @brief type used for representation of this quantity **/ - using repr_type = Repr; - ///@} - - static_assert(unit_concept); - static_assert(numeric_concept); - /* non-unity compile-time scale factors can arise during unit conversion; - * for example see method quantity::in_units_of() - */ - static_assert(std::same_as< typename Unit::scalefactor_type, std::ratio<1> >); - static_assert(std::same_as< typename Unit::canon_type, typename Unit::canon_type >); - - public: - /** @defgroup mpl-quantity-ctors constructors - **/ - ///@{ - constexpr quantity() = default; - constexpr quantity(quantity const & x) = default; - constexpr quantity(quantity && x) = default; - ///@} - - /** @defgroup quantity-named-ctors named constructors - **/ - ///@{ - /** @brief construct a unit quantity using @c unit_type - * - * @code - * auto q = qty::milliseconds(17) / qty::kilometers(23.0); - * q::unit_quantity(); // 1ms.km^-1 - * @endcode - **/ - static constexpr quantity unit_quantity() { return quantity(1); } - /** @brief promote representation to quantity. Same as multiplying by Unit - **/ - static constexpr auto promote(Repr x); - ///@} - - /** @addtogroup mpl-quantity-traits **/ - ///@{ - - /** @brief report this quantity's basis-power-unit type for a given basis dimension - * - * Example: - * @code - * auto q = 1.0 / (qty::milliseconds(5) * qty::seconds(100.0)); - * q.unit_cstr(); // "ms^-2" - * - * using tmp = q.find_bpu_t; - * - * tmp::c_native_dim; // dim::time - * tmp::c_native_unit; // native_unit_id::second - * tmp::scalefactor_type::num; // 1 - * tmp::scalefactor_type::den; // 1000 - * tmp::power_type::num; // -2 - * tmp::pwoer_type::den; // 1 - * @endcode - **/ - template - using find_bpu_t = unit_find_bpu_t; - - /** @brief report this quantity's scalefactor type for given basis dimension **/ - template - using basis_scale_type = typename find_bpu_t::scalefactor_type::type; - - ///@} - - /** @defgroup mpl-quantity-access-methods **/ - ///@{ - /** @brief get scale value (relative to unit) (@ref scale_) **/ - constexpr Repr scale() const { return scale_; } - /** @brief abbreviation for this quantity's units - * - * This string literal is constructed at compile-time by concatenating - * abbreviations for each basis-power-unit. - * For implementation see: - * * @c xo::unit::native_unit_abbrev_helper - * (in xo/unit/basis_unit.hpp) for each native dimension - * * @c xo::unit::scaled_native_unit_abbrev - * (in xo/unit/basis_unit.hpp) last-resort handling for scaled native dimensions - * * @c xo::unit::scaled_native_unit_abbrev - * (in xo/unit/unit.hpp) specializations for scaled native dimensions - **/ - static constexpr char const * unit_cstr() { return unit_abbrev_v.c_str(); } - ///@} - - /** @defgroup mpl-quantity-constants constants - **/ - ///@{ - /** @brief report exponent of @p BasisDim in dimension of this quantity - * - * For example: - * @code - * auto q = qty::milliseconds(5) * qty::seconds(1); - * int p1 = q.basis_power; // p1 == 2 - * int p2 = q.basis_power; // p2 == 0 - * @endcode - **/ - template - static constexpr PowerRepr basis_power = from_ratio::power_type>(); - ///@} - - /** @defgroup quantity-unit-conversion **/ - ///@{ - /** @brief convert to quantity representing the same amount, but changing units and perhaps representation. - * - * These two expressions are equivalent: - * @code - * q.with_unit(); - * quantity(q); - * @endcode - * - **/ - template - constexpr quantity with_unit() const { return *this; } - - /** - * @brief produce quantity scaled according to @p BasisUnit2, representing the same value as @c *this. - * - * For example: - * - * @code{.cpp} - * auto q1 = 1.0 / minutes(1) * kilograms(2.5); // q1 = 2.5kg.min^-1 - * auto q2 = q1.with_basis_unit(); // q2 in kg.ms^-1 - * @endcode - * - * Motivation is ability to chain rescaling to reach desired compound unit - * - * @code - * auto q3 = q1.with_basis_unit() - * .with_basis_unit(); // q3 in g.s^-1 - * @endcode - **/ - template - constexpr auto with_basis_unit() const { - static_assert(basis_unit_concept); - - using new_bpu_type = BasisUnit2::dim_type::front_type; - using old_bpulist_type = unit_type::dim_type; - using new_bpulist_type = di_replace_basis_scale::type; - using new_unit_type = wrap_unit, new_bpulist_type>; - -# ifdef NOT_USING_DEBUG - using xo::reflect::Reflect; - scope log(XO_DEBUG(true /*c_debug_flag*/)); - log && log(xtag("old_unit_type", Reflect::require()->canonical_name())); - log && log(xtag("new_unit_type", Reflect::require()->canonical_name())); -# endif - - return this->with_unit(); - } - - /** - * @brief express this quantity in the same units as @p q - * - * @pre @c *this and @p q must have the same dimension - * - * @param q take units from @c q::unit_type, ignoring @c q.scale() - * @return this amount, but expressed using the same units as @p q - **/ - template - auto with_units_from(Quantity q) const { - return this->with_units(); - } - - /** - * @brief express this quantity in units of @p Unit2. - * - * @p Unit2 specifies new units - * @p Repr2 specifies representation - * @return this amount, but expressed as a multiple of @p Unit2 - **/ - template - auto with_units() const { - Repr2 x = this->in_units_of(); - - return quantity::promote(x); - } - - /** - * @brief compute scale with respect to @p Unit2 - * - * @pre @c *this must have the same dimension as @p Unit2 - * - * @p Unit2 rescale in terms of this unit. - * @p Repr2 compute scale in this representation - * @return scale to use for @c quantity representing the same amount as @c *this. - **/ - template - Repr2 in_units_of() const { - // static_assert(dimension_of == dimension_of); // discard all the scaling values - - static_assert(same_dimension_v); - - using _convert_to_u2_type = unit_cartesian_product>; - - using exact_scalefactor_type = _convert_to_u2_type::exact_unit_type::scalefactor_type; - constexpr double c_scalefactor_inexact = _convert_to_u2_type::c_scalefactor_inexact; - - // _convert_u2_type - // - scalefactor_type - // - dim_type - // - canon_type - - /* if _convert_u2_type isn't dimensionless, then {Unit2, Unit} have different dimensions */ - - return ((this->scale_ * c_scalefactor_inexact * exact_scalefactor_type::num) / exact_scalefactor_type::den); - } - - /** - * @brief convert to quantity with representation @p Repr2 - * - * @return a quantity representing the same amount as @c *this, but using representation @p Repr2 - **/ - template - constexpr quantity with_repr() const { return quantity::promote(scale_); } - ///@} - - - /** @defgroup quantity-arithmeticsupport **/ - ///@{ - /** - * @brief multiply this quantity *x* by another quantity *y*. - * - * Result will propagate dimension and units appropriately. - * If *x* and *y* use conflicting scale factors for a dimension, - * adopt scalefactor from *x*. - * - * note: result will be a dimensionless value (e.g. type @c double) - * if units cancel. - * - * @pre @p Quantity2 must satisfy @c quantity_concept - * - * @param y multiply by this amount - * @return x.multiply(y) returns amount representing x*y - **/ - template - auto multiply(Quantity2 y) const { - //constexpr bool c_debug_flag = false; - //using Reflect = xo::reflect::Reflect; - - //scope log(XO_DEBUG(c_debug_flag)); - - static_assert(quantity_concept); - - /* unit: may have non-unit scalefactor_type */ - using unit_product_type = unit_cartesian_product; - using exact_unit_type = unit_product_type::exact_unit_type; - using norm_unit_type = normalize_unit_t; - - using exact_scalefactor_type = exact_unit_type::scalefactor_type; - constexpr double c_scalefactor_inexact = unit_product_type::c_scalefactor_inexact; - - using repr_type = std::common_type_t; - - repr_type r_scale = ((scale() * y.scale() * c_scalefactor_inexact * exact_scalefactor_type::num) - / exact_scalefactor_type::den); - -# ifdef NOT_USING_DEBUG - log && log(xtag("unit_product_type", Reflect::require()->canonical_name())); - log && log(xtag("exact_unit_type", Reflect::require()->canonical_name())); - log && log(xtag("norm_unit_type", Reflect::require()->canonical_name())); - log && log(xtag("exact_scalefactor_type", Reflect::require()->canonical_name())); - log && log(xtag("c_scalefactor_inexact", c_scalefactor_inexact)); - log && log(xtag("repr_type", Reflect::require()->canonical_name())); - log && log(xtag("repr_type", Reflect::require()->canonical_name())); -# endif - - return quantity::promote(r_scale); - } - - /** - * @brief multiply this quantity *x* by another quantity *y* - * - * Result will propagate dimension and units appropriately. - * If *x* and *y* use conflicting scale factors for a dimension, - * adopt scalefactor from *x*. - * - * note: result will be a dimensionless value (e.g. type @c double) - * if units cancel. - * - * @pre @p Quantity2 must satisfy @c quantity_concept - * - * @param y divide by this amount - * @return x.divide(y) returns amount representing x/y - **/ - template - auto divide(Quantity2 y) const { - using unit_divide_type = unit_divide; - using exact_unit_type = unit_divide_type::exact_unit_type; - using norm_unit_type = normalize_unit_t; - - using exact_scalefactor_type = exact_unit_type::scalefactor_type; - constexpr double c_scalefactor_inexact = unit_divide_type::c_scalefactor_inexact; - - using repr_type = std::common_type_t; - - repr_type r_scale = ((scale() * c_scalefactor_inexact * exact_scalefactor_type::num) - / (y.scale() * exact_scalefactor_type::den)); - -# ifdef NOT_USING_DEBUG - using xo::reflect::Reflect; - scope log(XO_DEBUG(true /*c_debug_flag*/)); - log && log(xtag("unit_divide_type", Reflect::require()->canonical_name())); - log && log(xtag("exact_unit_type", Reflect::require()->canonical_name())); - log && log(xtag("norm_unit_type", Reflect::require()->canonical_name())); - log && log(xtag("exact_scalefactor_type", Reflect::require()->canonical_name())); - log && log(xtag("c_scalefactor_inexact", c_scalefactor_inexact)); - log && log(xtag("r_scale", r_scale)); - log && log(xtag("repr_type", Reflect::require()->canonical_name())); -# endif - - return quantity::promote(r_scale); - } - - // quantity operator*=() - // quantity operator/=() - - /** - * @brief scale this quantity *x* by dimensionless amount @p y - * - * @return quantity representing @c x*y - **/ - template - auto scale_by(Repr2 y) const { - static_assert(!quantity_concept); - - using r_repr_type = std::common_type_t; - - r_repr_type r_scale = this->scale_ * y; - - //std::cerr << "quantity::scale_by: scale=" << scale << ", repr_type=" << reflect::Reflect::require()->canonical_name() << std::endl; - - return quantity::promote(r_scale); - } - - /** - * @brief divide this quantity *x* by dimensionless amount @p y - * - * @return quantity representing @c x/y - **/ - template - auto divide_by(Repr2 x) const { - using r_repr_type = std::common_type_t; - - r_repr_type r_scale = this->scale_ / x; - - return quantity::promote(r_scale); - } - - /** - * @brief divide dimensionless number @p x by this quantity @c y - * - * @return quantity representing @c x/y - **/ - template - auto divide_into(Repr2 x) const { - using r_unit_type = unit_invert_t; - using r_repr_type = std::common_type_t; - - r_repr_type r_scale = ((x * r_unit_type::scalefactor_type::num) - / (this->scale_ * r_unit_type::scalefactor_type::den)); - - return quantity::promote(r_scale); - } - ///@} - - /** @defgroup mpl-quantity-arithmetic **/ - ///@{ - /** @brief add quantity in-place - * - * @pre @p y must have the same dimension as @c *this. - * - * @param y quantity to add - * @return this quantity after adding y - **/ - template - quantity & operator+=(Quantity2 y) { - static_assert(same_dimension_v); - - /* relying on assignment that correctly converts-to-lhs-units */ - quantity y2 = y; - - this->scale_ += y2.scale(); - - return *this; - } - - /** @brief subtract quantity in-place - * - * @pre @p y must have the same dimensions as @c *this - * - * @param y quantity to subtract - * @return this quantity after subtracting y - **/ - template - quantity & operator-=(Quantity2 y) { - static_assert(same_dimension_v); - - quantity y2 = y; - - /* relying on assignment that correctly converts-to-lhs-units */ - this->scale_ -= y2.scale(); - - return *this; - } - ///@} - - /** @defgroup quantity-comparisonsupport **/ - ///@{ - /** @brief compare this quantity with another, return 3-way comparison - * - * @pre arguments must be quantities having the same dimension - * - * @param y rhs quantity to compare - * @return signed integer; {-ve, 0, +ve} when @c *this is {less than, equal, greater than} @p y - **/ - template - requires quantity_concept && same_dimension_v - auto compare(Quantity2 y) const { - /* convert y to same {units, repr} as *this */ - quantity y2 = y; - - auto cmp = (this->scale_ <=> y2.scale()); - - return cmp; - } - ///@} - - /** @defgroup quantity-comparison **/ - ///@{ - /** @brief 3-way comparison of two quantities - * - * @pre arguments must be quantities having the same dimension - * - * @param y rhs quantity to compare - * @return std::partial_ordering - **/ - template - requires quantity_concept && same_dimension_v - auto operator<=>(Quantity2 y) const { - return this->compare(y); - } - - /** @brief compare two quantities for equality - * - * Although compiler generates this (due to presence of 3-way comparison operator), - * it flags ambiguous overload when included alongside .h files from std::distribution. - * Look like ambiguity would need to be resolved by a header change - **/ - template - requires quantity_concept && same_dimension_v - bool operator==(Quantity2 y) const { - return std::is_eq(this->compare(y)); - } - - template - requires quantity_concept && same_dimension_v - bool operator!=(Quantity2 y) const { - return std::is_neq(this->compare(y)); - } - - - /** @addtogroup mpl-quantity-unit-conversion **/ - ///@{ - /** @brief convert to quantity with same dimension, different {unit_type, repr_type} - * - * @pre @c Quantity2 must have the same dimension as @c *this. - **/ - template - requires quantity_concept && same_dimension_v - constexpr operator Quantity2 () const { - /* avoid truncating precision when converting: - * use best available representation - */ - using tmp_repr_type = std::common_type_t; - - return Quantity2::promote(this->in_units_of()); - } - ///@} - - /** @defgroup mpl-quantity-print-support **/ - ///@{ - /** @brief write printed representation on stream - * - * @param os write on this output stream - **/ - void display(std::ostream & os) const { - os << this->scale() << unit_cstr(); - } - ///@} - - /** @defgroup mpl-quantity-assignment **/ - ///@{ - /** @brief copy constructor **/ - quantity & operator=(quantity const & x) = default; - /** @brief move constructor **/ - quantity & operator=(quantity && x) = default; - ///@} - - private: - explicit constexpr quantity(Repr x) : scale_{x} {} - - friend class promoter; - friend class promoter; - - private: - /** @brief quantity represents this multiple of a unit (that has compile-time outer-scalefactor of 1) **/ - Repr scale_ = 0; - }; /*quantity*/ - - template - constexpr auto - quantity::promote(Repr x) { - //std::cerr << "quantity::promote: x=" << x << ", R=" << reflect::Reflect::require()->canonical_name() << std::endl; - return promoter::promote(x); - } - - // ----- operator+ ----- - - template - inline constexpr Quantity1 operator+ (Quantity1 x, Quantity2 y) { - static_assert(same_dimension_v); - - /* convert y to match units used by x; - * would fail at compile time if this isn't well-defined - */ - Quantity1 y2 = y; - - return Quantity1::promote(x.scale() + y2.scale()); - } - - template - inline constexpr Quantity1 operator- (Quantity1 x, Quantity2 y) { - static_assert(std::same_as - < typename Quantity1::unit_type::dimension_type::canon_type, - typename Quantity2::unit_type::dimension_type::canon_type >); - - /* convert y to match units used by x */ - Quantity1 y2 = y; - - return Quantity1::promote(x.scale() - y2.scale()); - } - - template - inline Quantity operator- (Quantity x) { - return Quantity::promote(- x.scale()); - } - - template - inline constexpr auto operator* (Quantity1 x, Quantity2 y) { - static_assert(quantity_concept); - static_assert(quantity_concept); - - return x.multiply(y); - } - - /** e.g. DECLARE_LH_MULT(int32_t) **/ -# define DECLARE_LH_MULT(lhtype) \ - template \ - inline constexpr auto \ - operator* (lhtype x, Quantity y) { \ - static_assert(quantity_concept); \ - return y.scale_by(x); \ - } - - DECLARE_LH_MULT(int8_t); - DECLARE_LH_MULT(uint8_t); - DECLARE_LH_MULT(int16_t); - DECLARE_LH_MULT(uint16_t); - DECLARE_LH_MULT(int32_t); - DECLARE_LH_MULT(uint32_t); - DECLARE_LH_MULT(int64_t); - DECLARE_LH_MULT(uint64_t); - DECLARE_LH_MULT(float); - DECLARE_LH_MULT(double); -# undef DECLARE_LH_MULT - - /** e.g. DECLARE_RH_MULT(int32_t) **/ -# define DECLARE_RH_MULT(rhtype) \ - template \ - inline constexpr auto \ - operator* (Quantity x, rhtype y) { \ - static_assert(quantity_concept); \ - return x.scale_by(y); \ - } - - DECLARE_RH_MULT(int8_t); - DECLARE_RH_MULT(uint8_t); - DECLARE_RH_MULT(int16_t); - DECLARE_RH_MULT(uint16_t); - DECLARE_RH_MULT(int32_t); - DECLARE_RH_MULT(uint32_t); - DECLARE_RH_MULT(int64_t); - DECLARE_RH_MULT(uint64_t); - DECLARE_RH_MULT(float); - DECLARE_RH_MULT(double); -# undef DECLARE_LH_MULT - - template - inline constexpr auto operator/ (Quantity1 x, Quantity2 y) { - static_assert(quantity_concept); - static_assert(quantity_concept); - - return x.divide(y); - } - -# define DECLARE_LH_DIV(lhtype) \ - template \ - inline constexpr auto \ - operator/ (lhtype x, Quantity y) { \ - static_assert(quantity_concept); \ - return y.divide_into(x); \ - } - - DECLARE_LH_DIV(int8_t); - DECLARE_LH_DIV(uint8_t); - DECLARE_LH_DIV(int16_t); - DECLARE_LH_DIV(uint16_t); - DECLARE_LH_DIV(int32_t); - DECLARE_LH_DIV(uint32_t); - DECLARE_LH_DIV(int64_t); - DECLARE_LH_DIV(uint64_t); - DECLARE_LH_DIV(float); - DECLARE_LH_DIV(double); -# undef DECLARE_LH_DIV - -# define DECLARE_RH_DIV(rhtype) \ - template \ - inline constexpr auto \ - operator/ (Quantity x, rhtype y) { \ - static_assert(quantity_concept); \ - return x.divide_by(y); \ - } - - DECLARE_RH_DIV(int8_t) - DECLARE_RH_DIV(uint8_t) - DECLARE_RH_DIV(int16_t) - DECLARE_RH_DIV(uint16_t) - DECLARE_RH_DIV(int32_t) - DECLARE_RH_DIV(uint32_t) - DECLARE_RH_DIV(int64_t) - DECLARE_RH_DIV(uint64_t) - DECLARE_RH_DIV(float) - DECLARE_RH_DIV(double) -# undef DECLARE_RH_DIV - - template - inline std::ostream & - operator<< (std::ostream & os, quantity const & x) { - x.display(os); - return os; - } - - namespace qty { - // ----- mass ----- - - /** @brief create quantity representing @p x milligrams **/ - template - inline constexpr auto milligrams(Repr x) -> quantity { - return quantity::promote(x); - }; - - /** @brief create quantity representing @p x grams **/ - template - inline constexpr auto grams(Repr x) -> quantity { - return quantity::promote(x); - }; - - /** @brief create quantity representing @p x kilograms **/ - template - inline constexpr auto kilograms(Repr x) -> quantity { - return quantity::promote(x); - }; - - // ----- distance ----- - - /** @brief create quantity representing @p x millimeters **/ - template - inline constexpr auto millimeters(Repr x) -> quantity { - return quantity::promote(x); - } - - /** @brief create quantity representing @p x meters **/ - template - inline constexpr auto meters(Repr x) -> quantity { - return quantity::promote(x); - } - - /** @brief create quantity representing @p x kilometers **/ - template - inline constexpr auto kilometers(Repr x) -> quantity { - return quantity::promote(x); - } - - // ----- time ----- - - /** @brief create quantity representing @p x nanoseconds **/ - template - inline constexpr auto nanoseconds(Repr x) -> quantity { - return quantity::promote(x); - } - - /** @brief create quantity representing @p x microseconds **/ - template - inline constexpr auto microseconds(Repr x) -> quantity { - return quantity::promote(x); - } - - /** @brief create quantity representing @p x milliseconds **/ - template - inline constexpr auto milliseconds(Repr x) -> quantity { - return quantity::promote(x); - } - - /** @brief create quantity representing @p x seconds **/ - template - inline constexpr auto seconds(Repr x) -> quantity { - return quantity::promote(x); - } - - /** @brief create quantity representing @p x minutes **/ - template - inline constexpr auto minutes(Repr x) -> quantity { - return quantity::promote(x); - } - - /** @brief create quantity representing @p x hours **/ - template - inline constexpr auto hours(Repr x) -> quantity { - return quantity::promote(x); - } - - /** @brief create quantity representing @p x days (1 day = exactly 24 hours) **/ - template - inline constexpr auto days(Repr x) -> quantity { - return quantity::promote(x); - } - - // ----- time/volatility ----- - - /** quantity in units of 1/sqrt(1dy) **/ - template - inline constexpr auto volatility1d(Repr x) -> quantity { - return quantity::promote(x); - } - - /** quantity in units of 1/sqrt(30days) - **/ - template - inline constexpr auto volatility30d(Repr x) -> quantity { - return quantity::promote(x); - } - - /** quantity in units of 1/sqrt(250days) - **/ - template - inline constexpr auto volatility250d(Repr x) -> quantity { - return quantity::promote(x); - } - } /*namespace qty*/ - - namespace unit_qty { - /** @brief quantity with mass dimension, representing 1mg (1 milligram = 10^-3 grams) **/ - static constexpr auto milligram = qty::milligrams(1.0); - /** @brief quantity with mass dimension, representing 1g (1 gram) **/ - static constexpr auto gram = qty::grams(1.0); - /** @brief quantity with mass dimension, representing 1kg (1 kilogram = 1000 grams) **/ - static constexpr auto kilogram = qty::kilograms(1.0); - - /** @brief quantity with length dimension representing 1mm (10^-3 meters) **/ - static constexpr auto millimeter = qty::millimeters(1.0); - /** @brief quantity with length dimension representing 1m (1 meter) **/ - static constexpr auto meter = qty::meters(1.0); - /** @brief quantity with length dimension representing 1km (1 kilometer = 1000 meters) **/ - static constexpr auto kilometer = qty::kilometers(1.0); - - /** @brief quantity with time dimension representing 1ns (1 nanosecond = 10^-9 seconds) **/ - static constexpr auto nanosecond = qty::microseconds(1); - /** @brief quantity with time dimension representing 1us (1 microsecond = 10^-6 seconds) **/ - static constexpr auto microsecond = qty::microseconds(1); - /** @brief quantity with time dimension representing 1ms (1 milliseconds = 10^-3 seconds) **/ - static constexpr auto millisecond = qty::milliseconds(1); - /** @brief quantity with time dimension representing 1s (1 second) **/ - static constexpr auto second = qty::seconds(1); - /** @brief quantity with time dimension representing 1min (1 minute = 60 seconds) **/ - static constexpr auto minute = qty::minutes(1); - /** @brief quantity with time dimension representing 1hr (1 hour = 60 minutes) **/ - static constexpr auto hour = qty::hours(1); - /** @brief quantity with time dimension representing 1dy (1 day = 24 hours) **/ - static constexpr auto day = qty::days(1); - } - - } /*namespace unit*/ -} /*namespace xo*/ - -/* end quantity.hpp */ diff --git a/include/xo/unit/mpl/quantity_concept.hpp b/include/xo/unit/mpl/quantity_concept.hpp deleted file mode 100644 index 1edb2c59..00000000 --- a/include/xo/unit/mpl/quantity_concept.hpp +++ /dev/null @@ -1,23 +0,0 @@ -/** @file quantity_concept.hpp **/ - -#pragma once - -#include "unit_concept.hpp" -#include "numeric_concept.hpp" - -namespace xo { - namespace unit { - template - concept quantity_concept = requires(Quantity qty, typename Quantity::repr_type repr) - { - typename Quantity::unit_type; - typename Quantity::repr_type; - - { qty.scale() } -> std::same_as; - { Quantity::unit_cstr() } -> std::same_as; - { Quantity::unit_quantity() } -> std::same_as; - { Quantity::promote(repr) } -> std::same_as; - } && (unit_concept - && numeric_concept); - } /*namespace unit*/ -} /*namespace xo*/ diff --git a/include/xo/unit/mpl/ratio_concept.hpp b/include/xo/unit/mpl/ratio_concept.hpp deleted file mode 100644 index ecfeb4bf..00000000 --- a/include/xo/unit/mpl/ratio_concept.hpp +++ /dev/null @@ -1,13 +0,0 @@ -/* @file ratio_concept.hpp */ - -#pragma once - -#include - -namespace xo { - namespace unit { - template - concept ratio_concept = (std::is_signed_v - && std::is_signed_v); - } /*namespace unit*/ -} /*namespace xo*/ diff --git a/include/xo/unit/mpl/ratio_util.hpp b/include/xo/unit/mpl/ratio_util.hpp deleted file mode 100644 index 970068d8..00000000 --- a/include/xo/unit/mpl/ratio_util.hpp +++ /dev/null @@ -1,242 +0,0 @@ -/* @file ratio_util.hpp */ - -#pragma once - -#include "ratio_concept.hpp" -#include "stringliteral.hpp" -#include -#include - -namespace xo { - namespace unit { - // ----- ratio_floor ----- - - template - struct ratio_floor { - using type = std::ratio; - }; - - template - using ratio_floor_t = ratio_floor::type; - - // ----- ratio_frac ----- - - template - struct ratio_frac { - static_assert(ratio_concept); - - using type = std::ratio_subtract>::type; - }; - - template - using ratio_frac_t = ratio_frac::type; - - // ----- ratio_power ----- - - /** ratio to an integer power - * - * ratio_power::type - * - * yields a std::ratio representing Base^Power. - **/ - template - struct ratio_power_aux; - - template - struct ratio_power_aux { - - /** std::ratio representing Base^(-Power) **/ - using _inverse_ratio_type = typename ratio_power_aux::type; - - /** Base^(-Power)^-1 = Base^Power **/ - using type = std::ratio_divide, _inverse_ratio_type>; - - static_assert(ratio_concept); - }; - - template - struct ratio_power_aux { - using type = std::ratio<1>; - }; - - template - struct ratio_power_aux { - using type = Base; - }; - - template - struct ratio_power_aux { - /** Base^(Power/2) **/ - using _p2_ratio_type = ratio_power_aux::type; - /** Base^(Power%2) : - * - Base^0 if Power is even - * - Base^1 if Power is odd - **/ - using _x_ratio_type = ratio_power_aux::type; - - using type = std::ratio_multiply< _x_ratio_type, - std::ratio_multiply<_p2_ratio_type, - _p2_ratio_type> >; - - static_assert(ratio_concept); - }; - - // ----- ratio_power_v ----- - - /** compute Base^Power at compile time **/ - template - using ratio_power_t = ratio_power_aux::type; - - // ----- from_ratio ----- - - /** Example: - * from_ratio --> 0.1 - * from_ratio --> 0.8 - **/ - template - constexpr Repr from_ratio() { - static_assert(ratio_concept); - - return Ratio::num / static_cast(Ratio::den); - } - - // ----- ratio2str_aux ----- - - /* note: using struct wrapper because partial specialization of function templates - * is not allowed - */ - template - struct ratio2str_aux; - - template - struct ratio2str_aux { - static constexpr auto value = stringliteral_concat("(", - stringliteral_from_int_v().value_, - "/", - stringliteral_from_int_v().value_, - ")"); - }; - - template - struct ratio2str_aux { - /* note: using struct wrapper because partial specialization of function templates - * is not allowed - */ - static constexpr auto value = stringliteral_concat("-(", - stringliteral_from_int_v<-Num>().value_, - "/", - stringliteral_from_int_v().value_, - ")"); - }; - - template - struct ratio2str_aux { - static constexpr auto value = stringliteral_concat("-", - stringliteral_from_int_v<-Num>().value_); - }; - - template - struct ratio2str_aux { - static constexpr auto value = stringliteral_from_int_v(); - }; - - template <> - struct ratio2str_aux<1, 1, false> { - static constexpr auto value = stringliteral(""); - }; - - // ----- stringliteral_from_ratio ----- - - /** Example: - * - stringliteral_from_ratio -> "^(2,3)" - **/ - template - constexpr auto stringliteral_from_ratio() { - static_assert(ratio_concept); - - using lowest_terms_type = Ratio::type; - - return ratio2str_aux().value; - }; - - template - constexpr char const * cstr_from_ratio() { - static_assert(ratio_concept); - - using lowest_terms_type = Ratio::type; - - return ratio2str_aux().value.c_str(); - }; - - // ----- exponent2str_aux ----- - - /* note: using struct wrapper because partial specialization of function templates - * is not allowed - */ - template - struct exponent2str_aux; - - template - struct exponent2str_aux { - static constexpr auto value = stringliteral_concat("^(", - stringliteral_from_int_v().value_, - "/", - stringliteral_from_int_v().value_, - ")"); - }; - - template - struct exponent2str_aux { - /* note: using struct wrapper because partial specialization of function templates - * is not allowed - */ - static constexpr auto value = stringliteral_concat("^-(", - stringliteral_from_int_v<-Num>().value_, - "/", - stringliteral_from_int_v().value_, - ")"); - }; - - template - struct exponent2str_aux { - static constexpr auto value = stringliteral_concat("^", - stringliteral_from_int_v().value_); - }; - - template - struct exponent2str_aux { - /* Example: - * - exponent2str_aux<-1, 1, true> --> "^-1" - * - exponent2str_aux<-2, 1, true> --> "^-2" - */ - static constexpr auto value = stringliteral_concat("^", - stringliteral_from_int_v().value_); - }; - - template <> - struct exponent2str_aux<1, 1, false> { - static constexpr auto value = stringliteral(""); - }; - - // ----- stringliteral_from_exponent ----- - - template - constexpr auto stringliteral_from_exponent() { - static_assert(ratio_concept); - - return exponent2str_aux().value; - }; - - } /*namespace unit*/ -} /*namespace xo*/ - -/* end ratio_util.hpp */ diff --git a/include/xo/unit/mpl/stringliteral.hpp b/include/xo/unit/mpl/stringliteral.hpp deleted file mode 100644 index b3e68fe8..00000000 --- a/include/xo/unit/mpl/stringliteral.hpp +++ /dev/null @@ -1,170 +0,0 @@ -/* @file stringliteral.hpp */ - -#pragma once - -#include -#include -#include -#include - -namespace xo { - namespace unit { - - // ----- stringliteral ----- - - /** @class stringliteral - * - * @brief class to represent a literal string at compile time, for use as template argument - **/ - template - struct stringliteral { - constexpr stringliteral() { if (N > 0) value_[0] = '\0'; } - constexpr stringliteral(const char (&str)[N]) { std::copy_n(str, N, value_); } - constexpr int size() const { return N; } - - constexpr char const * c_str() const { return value_; } - - char value_[N]; - }; - - template < typename First, typename... Rest > - constexpr auto - all_same_v = std::conjunction_v< std::is_same... >; - - /** args and result must be stringliteral **/ - template < typename... Ts> - constexpr auto - stringliteral_concat(Ts && ... args) - { -#ifdef NOT_USING - static_assert(all_same_v...>, - "string must share the same char type"); - - using char_type = std::remove_const_t< std::remove_pointer_t < std::common_type_t < Ts... > > >; -#endif - using char_type = char; - - /** n1: total number of bytes used by arguments **/ - constexpr size_t n1 = (sizeof(Ts) + ...); - /** z1: each string arg has a null terminator included in its size, - * z1 is the number of arguments in parameter pack Ts, - * which equals the number of null terminators used - **/ - constexpr size_t z1 = sizeof...(Ts); - - /** n: number of chars in concatenated string. +1 for final null **/ - constexpr size_t n - = (n1 / sizeof(char_type)) - z1 + 1; - - stringliteral result; - - size_t pos = 0; - - auto detail_concat = [ &pos, &result ](auto && arg) { - constexpr auto count = (sizeof(arg) - sizeof(char_type)) / sizeof(char_type); - - std::copy_n(arg, count, result.value_ + pos); - pos += count; - }; - - (detail_concat(args), ...); - - return result; - } - - template - constexpr auto - stringliteral_compare(stringliteral && s1, stringliteral && s2) - { - return std::string_view(s1.value_) <=> std::string_view(s2.value_); - } - - // ----- literal_size ----- - - /** @brief compute number of chars needed to stringify an int **/ - template < int d, bool signbit = std::signbit(d) > - struct literal_size; - - template < int d > - struct literal_size { - /* d < 0 */ - static constexpr int size = 1 + literal_size<-d, false>::size; - }; - - template < int d > - struct literal_size { - /* d >= 0 */ - static constexpr int size = 1 + literal_size::size; - }; - - template <> struct literal_size<0, false> { static constexpr int size = 1; }; - template <> struct literal_size<1, false> { static constexpr int size = 1; }; - template <> struct literal_size<2, false> { static constexpr int size = 1; }; - template <> struct literal_size<3, false> { static constexpr int size = 1; }; - template <> struct literal_size<4, false> { static constexpr int size = 1; }; - template <> struct literal_size<5, false> { static constexpr int size = 1; }; - template <> struct literal_size<6, false> { static constexpr int size = 1; }; - template <> struct literal_size<7, false> { static constexpr int size = 1; }; - template <> struct literal_size<8, false> { static constexpr int size = 1; }; - template <> struct literal_size<9, false> { static constexpr int size = 1; }; - - template < int d > - constexpr int literal_size_v = literal_size::size; - - // ----- literal_from_digit ----- - - constexpr auto /*stringliteral<2>*/ stringliteral_from_digit( int d ) { - return stringliteral({ static_cast('0' + d), '\0' }); - } - -#ifdef NOT_YET_22mar24 - template < int d > - struct literal_from_digit; - - template <> struct literal_from_digit<0> { static constexpr auto value = stringliteral("0"); }; - template <> struct literal_from_digit<1> { static constexpr auto value = stringliteral("1"); }; - template <> struct literal_from_digit<2> { static constexpr auto value = stringliteral("2"); }; - template <> struct literal_from_digit<3> { static constexpr auto value = stringliteral("3"); }; - template <> struct literal_from_digit<4> { static constexpr auto value = stringliteral("4"); }; - template <> struct literal_from_digit<5> { static constexpr auto value = stringliteral("5"); }; - template <> struct literal_from_digit<6> { static constexpr auto value = stringliteral("6"); }; - template <> struct literal_from_digit<7> { static constexpr auto value = stringliteral("7"); }; - template <> struct literal_from_digit<8> { static constexpr auto value = stringliteral("8"); }; - template <> struct literal_from_digit<9> { static constexpr auto value = stringliteral("9"); }; - - template < int d > - constexpr auto literal_from_digit_v() { return literal_from_digit::value; } -#endif - - // ----- stringliteral_from_int ----- - - template < int D, int N = literal_size_v, bool signbit = std::signbit(D) > - struct stringliteral_from_int; - - template < int D, int N = literal_size_v, bool signbit = std::signbit(D) > - constexpr auto stringliteral_from_int_v() { return stringliteral_from_int::value; } - - template < int D > - struct stringliteral_from_int< D, 1, false > { - static constexpr auto value = stringliteral_from_digit(D); - }; - - template < int D, int N > - struct stringliteral_from_int< D, N, false > { - static constexpr auto _prefix = stringliteral_from_int_v< D / 10, N - 1, false >(); - static constexpr auto _suffix = stringliteral_from_digit(D % 10); - - static constexpr auto value = stringliteral_concat(_prefix.value_, _suffix.value_); - }; - - template < int D, int N > - struct stringliteral_from_int< D, N, true > { - static constexpr auto _suffix = stringliteral_from_int_v< -D, N - 1, false>(); - - static constexpr auto value = stringliteral_concat("-", _suffix.value_); - }; - - } /*namespace unit*/ -} /*namespace xo*/ - -/* end stringliteral.hpp */ diff --git a/include/xo/unit/mpl/unit.hpp b/include/xo/unit/mpl/unit.hpp deleted file mode 100644 index 54c8fffc..00000000 --- a/include/xo/unit/mpl/unit.hpp +++ /dev/null @@ -1,395 +0,0 @@ -/* @file unit.hpp */ - -#pragma once - -#include "unit_concept.hpp" -#include "dimension_impl.hpp" - -namespace xo { - namespace unit { - // ----- wrap_unit ----- - - template - struct wrap_unit { - static_assert(ratio_concept); - static_assert(bpu_list_concept); - - //using _norm_type = bpu_normalize; - - using scalefactor_type = Scalefactor; - using dim_type = BpuList; - - /* canon_type just orders dimensions by increasing native_dim_id */ - using canon_type = canonical_impl::dim_type; - - static_assert(bpu_list_concept); - }; - - // ----- normalize_unit ----- - - /* like Unit, but with scalefactor 1 */ - template - struct normalize_unit { - static_assert(unit_concept); - - using type = wrap_unit, typename Unit::dim_type>; - }; - - template - using normalize_unit_t = normalize_unit::type; - - // ----- dimensionless_v ----- - - template - constexpr auto dimensionless_v = std::same_as; - - // ----- unit_find_bpu ----- - - /** @brief find basis-power-unit matching native_dim_id - * - * Constructs dimensionless native_bpu if no match - **/ - template - struct unit_find_bpu { - using type = di_find_bpu::type; - }; - - template - using unit_find_bpu_t = unit_find_bpu::type; - - // ----- unit_abbrev_v ----- - - /** @brief canonical stringliteral abbreviation for dimension D. **/ - template - constexpr auto unit_abbrev_v = di_assemble_abbrev::value; - - // ----- unit_invert ----- - - template - struct unit_invert { - static_assert(unit_concept); - - using _sf = std::ratio_divide, typename U::scalefactor_type>; - using _di = di_invert< typename U::dim_type >::type; - - using type = wrap_unit< _sf, _di >; - - static_assert(unit_concept); - }; - - template - using unit_invert_t = unit_invert::type; - - // ----- unit_cartesian_product ----- - - template - struct unit_cartesian_product { - /* when a basis dimension (represented by a bpu<..>::c_native_dim) - * is present in both {U1, U2}, we need to pick a common unit - * (represented by bpu<..>::scalefactor_type). - * - * scale factors from such conversions are collected in: - * 1a. _mult_type::outer_scalefactor_type (compile-time exact representation using std::ratio) - * 1b. _mult_type::outer_scalefactor_inexact (compile-time constexpr) - */ - - static_assert(unit_concept); - static_assert(unit_concept); - - /* _mult_type -> describes product dimension */ - using _mult_type = di_cartesian_product< - typename U1::dim_type, - typename U2::dim_type>; - - /* compile-time exact scalefactor for product dimension - * (distilled from any forced rescaling) - */ - using _mult_sf_type = _mult_type::outer_scalefactor_type; - /* bpulist specifying basis factors (possibly to rational powers) in product dimension */ - using _mult_di_type = _mult_type::bpu_list_type; - - /* note: inexact scalefactor doesn't come up here. - * It's not present in unit types, only appears as byproduct - * of products/ratios of units - */ - using _sf1_type = typename std::ratio_multiply< - typename U1::scalefactor_type, - typename U2::scalefactor_type>::type; - - using _sf_type = typename std::ratio_multiply<_mult_sf_type, _sf1_type>::type; - - /* note: we can compute inexact scale factor, - * but can't make it a template argument - */ - using exact_unit_type = wrap_unit< _sf_type, _mult_di_type >; - - static constexpr double c_scalefactor_inexact = _mult_type::c_outer_scalefactor_inexact; - - static_assert(unit_concept); - }; - - /* WARNING: omits inexact scalefactor */ - template - using unit_cartesian_product_t = unit_cartesian_product::exact_unit_type; - - // ----- unit_divide ----- - - template - struct unit_divide { - static_assert(unit_concept); - static_assert(unit_concept); - - using _mult_type = unit_cartesian_product>; - using exact_unit_type = _mult_type::exact_unit_type; - - static constexpr double c_scalefactor_inexact = _mult_type::c_scalefactor_inexact; - }; - - /* WARNING: omits inexact scalefactor */ - template - using unit_divide_t = unit_divide::exact_unit_type; - - // ----- same_dimension ----- - - /* true iff U1 and U2 represent the same dimension, i.e. differ only in dimensionless scaling factor - * - * To verify scale also, use same_unit instead - */ - template - struct same_dimension { - static_assert(unit_concept); - static_assert(unit_concept); - - using _unit_ratio_type = typename unit_cartesian_product>::exact_unit_type; - - static_assert(std::same_as); - - static constexpr bool value = std::same_as; - }; - - template - constexpr bool same_dimension_v = same_dimension::value; - - // ----- same_unit ----- - - template - struct same_unit { - static_assert(unit_concept); - static_assert(unit_concept); - - using _unit_ratio_type = unit_cartesian_product>; - using _unit_exact_type = typename _unit_ratio_type::exact_unit_type; - using _unit_scalefactor_type = _unit_exact_type::scalefactor_type; - static constexpr double c_unit_ratio_inexact = _unit_ratio_type::c_scalefactor_inexact; - - static_assert(std::same_as<_unit_scalefactor_type, std::ratio<1>>); - static_assert(std::same_as); - - static constexpr bool value = (std::same_as<_unit_scalefactor_type, std::ratio<1>> - && (c_unit_ratio_inexact == 1.0) - && std::same_as); - }; - - template - constexpr bool same_unit_v = same_unit::value; - - // ----- unit_conversion_factor ----- - - template - struct unit_conversion_factor { - static_assert(same_dimension_v); - - using _unit_ratio_type = typename unit_cartesian_product>::exact_unit_type; - using type = _unit_ratio_type::scalefactor_type; - static constexpr double c_scalefactor_inexact = _unit_ratio_type::c_scalefactor_inexact; - }; - - /** conversion factor from U1 to U2: - * U1 = x.U2 - * with: - * x = R::num / R::den - * R = unit_conversion_factor_t - * - * WARNING: omits inexact scalefactor unit_conversion_factor::c_scalefactor_inexact - **/ - template - using unit_conversion_factor_t = unit_conversion_factor::type; - - // ----- units ----- - - namespace units { - /* computing abbreviations: - * - unit_abbrev_v :: stringliteral<...> - * - unit_abbrev_v.c_str() :: const char * - * - * relies on - * - di_assemble_abbrev, di_assemble_abbrev_helper [dimension_impl.hpp] - * - * - bpu_assemble_abbrev() [native_bpu.hpp] - * - bpu_assemble_abbrev_helper< native_bpu::c_native_dim, - * native_bpu::scalefactor_type, - * native_bpu::power_type > - * -> stringliteral - * - * + can specialize for specific combinations - * - * - native_unit_abbrev_helper< native_bpu::c_native_dim, - * native_bpu::power_type > - */ - - // ----- weight ----- - - /** @brief a unit type representing 1mg (10^-3 grams) **/ - using milligram = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; - - template <> - struct scaled_native_unit_abbrev> { - static constexpr auto value = stringliteral("mg"); - }; - - using gram = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; - using kilogram = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; - - template <> - struct scaled_native_unit_abbrev> { - static constexpr auto value = stringliteral("kg"); - }; - - // ----- distance ----- - - using millimeter = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; - template <> - struct scaled_native_unit_abbrev> { - static constexpr auto value = stringliteral("mm"); - }; - - using meter = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; - - using kilometer = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; - template <> - struct scaled_native_unit_abbrev> { - static constexpr auto value = stringliteral("km"); - }; - - // ----- time ----- - - using nanosecond = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; - - template <> - struct scaled_native_unit_abbrev> { - static constexpr auto value = stringliteral("ns"); - }; - - using microsecond = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; - - template <> - struct scaled_native_unit_abbrev> { - static constexpr auto value = stringliteral("us"); - }; - - using millisecond = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; - - template <> - struct scaled_native_unit_abbrev> { - static constexpr auto value = stringliteral("ms"); - }; - - using second = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; - using minute = wrap_unit< std::ratio<1>, bpu_node< bpu> > >; - template <> - struct scaled_native_unit_abbrev> { - static constexpr auto value = stringliteral("min"); - }; - - using hour = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; - - template <> - struct scaled_native_unit_abbrev> { - static constexpr auto value = stringliteral("hr"); - }; - - using day = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; - - template <> - struct scaled_native_unit_abbrev> { - static constexpr auto value = stringliteral("dy"); - }; - - using month = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; - - template <> - struct scaled_native_unit_abbrev> { - static constexpr auto value = stringliteral("mo"); - }; - - using yr250 = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; - - template <> - struct scaled_native_unit_abbrev> { - static constexpr auto value = stringliteral("yr250"); - }; - - // ------ volatility ------ - - /* volatility in units of 1/sqrt(1day) - * volatility^2 in units of 1/day - */ - using volatility_1d = wrap_unit< std::ratio<1>, - bpu_node< bpu, - std::ratio<-1,2>> > >; - - /* volatility in units of 1/sqrt(30day) - * volatility^2 in units of 1/(30day) - */ - using volatility_30d = wrap_unit< std::ratio<1>, - bpu_node< bpu, - std::ratio<-1,2>> > >; - - /* volatility in units of 1/sqrt(250day) - * volatility^2 in units of 1/(250day) - */ - using volatility_250d = wrap_unit< std::ratio<1>, - bpu_node< bpu, - std::ratio<-1,2>> > >; - - using currency = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; - using price = wrap_unit< std::ratio<1>, - bpu_node< bpu> > >; - } /*namespace units*/ - } /*namespace unit*/ -} /*namespace xo*/ - -/* end unit.hpp */ diff --git a/include/xo/unit/mpl/unit_concept.hpp b/include/xo/unit/mpl/unit_concept.hpp deleted file mode 100644 index 8c5fe59c..00000000 --- a/include/xo/unit/mpl/unit_concept.hpp +++ /dev/null @@ -1,49 +0,0 @@ -/* @file unit_concept.hpp */ - -#pragma once - -#include "dimension_concept.hpp" - -namespace xo { - namespace unit { - /** @brief concept for a Unit type, suitable for use with the quantity template - * - * Example: - * @code - * using namespace xo::unit; - * static_assert(unit_concept); - * @endcode - **/ - template - concept unit_concept = requires(Unit unit) - { - typename Unit::scalefactor_type; - typename Unit::dim_type; - typename Unit::canon_type; - } - && (ratio_concept - && bpu_list_concept - && bpu_list_concept); - - - /** @brief concept for a Unit type, that contains exactly one basis dimension - * - * Example: - * @code - * using namespace xo::unit - * static_assert(basis_unit_concept); - * @endcode - **/ - template - concept basis_unit_concept = requires(Unit unit) - { - typename Unit::dim_type; - typename Unit::dim_type::rest_type; - } - && (std::same_as) - && (unit_concept); - } /*namespace unit*/ -} /*namespace xo*/ - - -/* end unit_concept.hpp */ From 1b83bd7be235c3a5199182f9742c3a52838943ab Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 30 May 2024 16:34:25 -0400 Subject: [PATCH 0992/2524] xo-cmake: tweaks for toplevel options --- .gitignore | 1 + cmake/xo_macros/xo_cxx.cmake | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 7b6765fd..04e2da49 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +.projectile .build compile_commands.json diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 3f683129..4533f929 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -1,6 +1,7 @@ +option(XO_ENABLE_EXAMPLES "enable building example programs" OFF) macro(xo_cxx_config_message) - message(STATUS "GUESSED_CMAKE_CMD=cmake -DXO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE} -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_DOCDIR=${CMAKE_INSTALL_DOCDIR} -B ${CMAKE_BINARY_DIR}") + message(STATUS "GUESSED_CMAKE_CMD=cmake -DXO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE} -DENABLE_TESTING=${ENABLE_TESTING} -DXO_ENABLE_EXAMPLES=${XO_ENABLE_EXAMPLES} -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_DOCDIR=${CMAKE_INSTALL_DOCDIR} -B ${CMAKE_BINARY_DIR}") message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") message(STATUS "CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") endmacro() @@ -52,11 +53,7 @@ macro(xo_cxx_toplevel_options2) endmacro() macro(xo_cxx_toplevel_options3) - enable_language(CXX) - xo_toplevel_compile_options() - enable_testing() - add_custom_target(all_libraries) - add_custom_target(all_utest_executables) + xo_cxx_toplevel_options2() xo_toplevel_config2() endmacro() From ae69a418ffd46b9b9a9c2da18fb94c87689e6ed7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 30 May 2024 16:36:20 -0400 Subject: [PATCH 0993/2524] xo-ratio: streamline example+utest build --- .gitignore | 2 ++ CMakeLists.txt | 7 +---- example/ex1/CMakeLists.txt | 13 ++++------ utest/CMakeLists.txt | 53 +++++++------------------------------- 4 files changed, 17 insertions(+), 58 deletions(-) diff --git a/.gitignore b/.gitignore index 13c0afb7..6f77977a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# emacs project control file +.projectile # clangd working space (see emacs+lsp) .cache # typical cmake build directory (source-tree-nephew) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92061c0f..6a365f30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,12 +8,7 @@ enable_language(CXX) include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) -xo_cxx_toplevel_options2() - -# ---------------------------------------------------------------- -# cmake -DCMAKE_BUILD_TYPE=coverage -# -xo_toplevel_coverage_config2() +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt index 224e5690..abe9347c 100644 --- a/example/ex1/CMakeLists.txt +++ b/example/ex1/CMakeLists.txt @@ -3,13 +3,10 @@ set(SELF_EXE xo_ratio_ex1) set(SELF_SRCS ex1.cpp) -add_executable(${SELF_EXE} ${SELF_SRCS}) -xo_include_options2(${SELF_EXE}) - -# ---------------------------------------------------------------- -# dependencies.. - -xo_self_dependency(${SELF_EXE} xo_ratio) -#xo_dependency(${SELF_EXE} reflect) +if (XO_ENABLE_EXAMPLES) + add_executable(${SELF_EXE} ${SELF_SRCS}) + xo_include_options2(${SELF_EXE}) + xo_self_dependency(${SELF_EXE} xo_ratio) +endif() # end CMakeLists.txt diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index a0cdc40e..052961ec 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -5,53 +5,18 @@ set(SELF_SRCS ratio_utest_main.cpp ratio.test.cpp) -xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS}) +if (ENABLE_TESTING) + xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS}) -# ---------------------------------------------------------------- -# in coverage build, target to build+install coverage report + xo_utest_coverage_config2() -if (XO_SUBMODULE_BUILD) - # in submodule build, generate aggregate coverage report - # for all xo libraries -else() - set(CCOV_OUTPUT_DIR ${PROJECT_BINARY_DIR}/ccov/html) - set(CCOV_INDEX_FILE ${CCOV_OUTPUT_DIR}/index.html) - set(CCOV_REPORT_EXE ${PROJECT_BINARY_DIR}/gen-ccov) - # CMAKE_INSTALL_DOCDIR - # =default=> DATAROOTDIR/doc/PROJECT_NAME - # =default=> CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring - set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov) + # ---------------------------------------------------------------- + # dependencies.. - # 'test' target should always be out-of-date - # - # DEPENDS: reminder - can't put 'test' here, requires 'all' target - # - add_custom_command( - OUTPUT ${CCOV_INDEX_FILE} - DEPENDS ${SELF_EXE} - COMMAND ${CCOV_REPORT_EXE} - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - COMMENT "Generating coverage report -> [${CCOV_OUTPUT_DIR}]") - - add_custom_target( - ccov - DEPENDS ${CCOV_INDEX_FILE} ${SELF_EXE}) - - # OPTIONAL: quietly skip this step if ccov report not generated - install( - DIRECTORY ${CCOV_OUTPUT_DIR} - FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ - DESTINATION ${CCOV_INSTALL_DOCDIR} - COMPONENT Documentation - OPTIONAL) + xo_self_headeronly_dependency(${SELF_EXE} xo_ratio) + xo_dependency(${SELF_EXE} randomgen) + xo_dependency(${SELF_EXE} indentlog) + xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) endif() -# ---------------------------------------------------------------- -# dependencies.. - -xo_self_headeronly_dependency(${SELF_EXE} xo_ratio) -xo_dependency(${SELF_EXE} randomgen) -xo_dependency(${SELF_EXE} indentlog) -xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) - # end CMakeLists.txt From 32c3d8e44e376ba1a8950b0ad5378afaceda52c2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 10 Jun 2024 12:03:22 -0400 Subject: [PATCH 0994/2524] xo-unit: + currency() named-ctor --- include/xo/unit/quantity.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index 5d549b9c..e986b95f 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -974,6 +974,14 @@ namespace xo { } /*namespace qty*/ + namespace qty { + // ----- currency ----- + + /** create quantity representing @p x units of currency, with compile-time unit representation **/ + template + inline constexpr auto currency(Repr x) { return quantity(x); } + } + namespace qty { // ----- volatility ----- From 530a569cfa01de2a82b0de421e2f7d7b3754d801 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 10 Jun 2024 12:03:34 -0400 Subject: [PATCH 0995/2524] xo-unit: + quantity methods is_negative()/is_positive() --- include/xo/unit/quantity.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index e986b95f..ac75e04a 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -78,6 +78,11 @@ namespace xo { /** @brief s_unit in quantity representing amount (@c scale_ * @c s_unit) **/ constexpr const unit_type & unit() const { return s_scaled_unit; } + /** @brief true iff this quantity is strictly negative **/ + constexpr bool is_negative() const { return scale_ < Repr{0}; } + /** @brief true iff this quantity is strictly positive **/ + constexpr bool is_positive() const { return scale_ > Repr{0}; } + /** @brief true iff this quantity represents a dimensionless value **/ static constexpr bool is_dimensionless() { return s_scaled_unit.is_dimensionless(); From cf41b7a4830ca24dd0d6f08393de9a019697a936 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 10 Jun 2024 12:04:06 -0400 Subject: [PATCH 0996/2524] xo-unit: bugfix: units for reciprocal --- include/xo/unit/quantity.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/xo/unit/quantity.hpp b/include/xo/unit/quantity.hpp index ac75e04a..4a232b5b 100644 --- a/include/xo/unit/quantity.hpp +++ b/include/xo/unit/quantity.hpp @@ -219,8 +219,8 @@ namespace xo { constexpr auto divide_into(Dimensionless x) const { using r_repr_type = std::common_type_t; - return quantity(static_cast(x) / this->scale_, - s_scaled_unit.reciprocal()); + return quantity + (static_cast(x) / this->scale_); } ///@} From 0200808f904be7a73f6cc8322ec67023c578ae5b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 10 Jun 2024 12:04:20 -0400 Subject: [PATCH 0997/2524] xo-unit: utest: test currency() named ctor --- utest/quantity.test.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/utest/quantity.test.cpp b/utest/quantity.test.cpp index ed1b4f4e..a02da256 100644 --- a/utest/quantity.test.cpp +++ b/utest/quantity.test.cpp @@ -371,6 +371,21 @@ namespace xo { // /* milligrams:grams */ } /*TEST_CASE(quantity.mult)*/ + TEST_CASE("quantity.currency", "[quantity.currency]") { + constexpr bool c_debug_flag = true; + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.quantity.time")); + + constexpr auto ccy = qty::currency(1.0); + static_assert(quantity_concept); + static_assert(sizeof(ccy) == sizeof(double)); + static_assert(ccy.scale() == 1.0); + static_assert(ccy.abbrev() == flatstring("ccy")); + + log && log(xtag("ccy.abbrev", ccy.abbrev())); + REQUIRE(tostr(ccy) == "1ccy"); + } /*TEST_CASE(quantity.currency)*/ + TEST_CASE("quantity.mult2", "[quantity.mult]") { constexpr auto ms = qty::milliseconds(1.0); From 1727a7b97b7679a97919b905a941c1205924101d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 10 Jun 2024 12:04:35 -0400 Subject: [PATCH 0998/2524] xo-unit: ++ github README --- README.md | 66 ++++++++++++++++++++++++++++++++++++++---------- docs/install.rst | 2 +- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a85c8d4a..228f14b8 100644 --- a/README.md +++ b/README.md @@ -11,46 +11,86 @@ Similar to `boost::units`, but: - xo-unit documentation [under construction]: [documentation](https://rconybea.github.io/web/xo-unit/html/index.html) - unit test coverage here: [coverage](https://rconybea.github.io/web/xo-unit/ccov/html/index.html) +## Example + +``` +#include "xo/unit/quantity.hpp" +#include "xo/unit/quantity_iostream.hpp" + +namespace q = xo::qty::qty; +namespace u = xo::qty::u; + +constexpr auto t = q::minutes(2); +constexpr auto d = q::kilometers(2.5); + +constexpr auto t2 = t*t; // unit will be min^-2 +constexpr auto a = d / t2; // unit will be km.min^-2 + +// convert to m.s^-2 +constexpr quantity a2 = a; + +//constexpr quantity a3 = a; // dimension mismatch, will not compile + +// get dimensionless scale value +double x = a2.scale(); +``` + ## Getting Started -### build + install dependencies +See [full install instructions](https://rconybea.github.io/web/xo-unit/html/install.html) for other installation strategies. -- [github/Rconybea/reflect](https://github.com/Rconybea/reflect) +### build + install upstream dependencies + +- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) +- [github/Rconybea/xo-flatstring](https://github.com/Rconybea/xo-flatstring) +- [github/Rconybea/xo-ratio](https://github.com/Rconybea/xo-ratio) + +(Below assumes they're installed using some common value for `PREFIX`) + +### fetch xo-unit +``` +$ cd ~/proj +$ git clone https://github.com/rconybea/xo-unit +``` ### build + install ``` $ cd xo-unit -$ mkdir .build -$ cd .build $ PREFIX=/usr/local # or wherever you prefer -$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} .. -$ make -$ make install +$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S . -B .build +$ cmake --build .build -j +$ cmake --install .build ``` ### build documentation ``` $ cd xo-unit -$ cmake --build .build -- sphinx +$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} --build .build -- sphinx ``` -When this completes, point local browser to `xo-unit/.build/docs/sphinx/index.html`. +When this completes, can point local browser to `xo-unit/.build/docs/sphinx/index.html`. ### build for unit test coverage + +Note that unit tests involve additional dependencies: +- [github/Rconybea/xo-indentlog](https://github.com/Rconybea/indentlog) +- [github/Rconybea/xo-randomgen](https://github.com/Rconybea/randomgen) + ``` $ cd xo-unit $ mkdir .build-ccov -$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_PREFIX_PATH=${PREFIX} -B .build-ccov +$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_INSTALL_PREFIX=${PREFIX} -DENABLE_TESTING=1 -B .build-ccov ``` -run coverage-enabled unit tests +run coverage-enabled unit tests: ``` $ cmake --build .build-ccov -- test ``` -generate html+text coverage report +generate html+text coverage report: ``` -$cmake --build .build-ccov -- ccov +$ .build-ccov/gen-ccov ``` +To see coverage, can point local browser to `xo-unit/.build-ccov/ccov/html/index.html` ### LSP support ``` diff --git a/docs/install.rst b/docs/install.rst index ded68291..1bab499b 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -65,7 +65,7 @@ Installing from source ---------------------- Install scripts for `xo-unit`, `xo-ratio` and `xo-flatstring` depend on shared cmake macros -and a bootstrap script `xo-cmake-config` fro `xo-cmake`. +and a bootstrap script `xo-cmake-config` from `xo-cmake`. Preamble: From cd62475738fc3c8be92cffa24f19035781824734 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 12 Jun 2024 12:30:54 -0400 Subject: [PATCH 0999/2524] xo-cmake: + xo-bootstrap-macros.cmake --- CMakeLists.txt | 1 + share/xo-macros/xo-bootstrap-macros.cmake | 35 +++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 share/xo-macros/xo-bootstrap-macros.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index be901316..0e28d3fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ install( FILES "share/xo-macros/gen-ccov.in" "share/xo-macros/Doxyfile.in" + "share/xo-macros/xo-bootstrap-macros.cmake" PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE DESTINATION ${CMAKE_INSTALL_DATADIR}/xo-macros ) diff --git a/share/xo-macros/xo-bootstrap-macros.cmake b/share/xo-macros/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/share/xo-macros/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From fb63bd1261756cb2645a13769508ecf121794bf0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 12 Jun 2024 12:31:37 -0400 Subject: [PATCH 1000/2524] ++ README improvements -- bring up-to-date --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c5c6353d..db3b4dd8 100644 --- a/README.md +++ b/README.md @@ -6,23 +6,54 @@ Collects cmake macros to be shared across XO projects (e.g. indentlog, reflect, - support for both manyrepo and monorepo projects - support for generating cmake `xxxConfig.cmake` files, so cmake `find_package()` works reliably +- support for header-only libraries +- support for pybind11 libraries +- documentation generation using doxygen + breathe + sphinx +- code coverage using ccov + lcov -## Example +## Getting Started + +### copy repo -In some XO project `foo`: ``` -$ cd build -$ PREFIX=/usr/local # or wherever you prefer -$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} .. -$ make install +$ git clone https://github.com:rconybea/xo-cmake.git ``` +### configure + install +``` +$ cd xo-cmake +$ cmake -DCMAKE_INSTALL_PREFIX=/usr/local -B .build -S . # ..or desired prefix +$ cmake --install .build +``` + +## use from a cmake project + +In some project `foo`: +``` +$ cd foo +$ mkdir cmake +$ cp $PREFIX/share/xo-macros/xo-bootstrap-macros.cmake cmake/ +``` + +`xo-bootstrap-macros-cmake` has two vital jobs: +1. set `XO_CMAKE_CONFIG_EXECUTABLE` (locate `xo-cmake-config`) +2. set `CMAKE_MODULE_PATH` (obtained from `xo-cmake-config --cmake-module-path`) + then in `foo/CMakeLists.txt`: ``` -include(xo_macros/xo_cxx) +include(cmake/xo-bootstrap-macros.cmake) ``` -when configuring `foo`: +Now as long as `$PREFIX/bin` is in `PATH`: ``` -$ cmake -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake path/to/foo +$ cd mybuild +$ cmake path/to/foo/source +``` + +### or set `XO_CMAKE_CONFIG_EXECUTABLE` and `CMAKE_MODULE_PATH` + +In some project `foo`: +``` +$ cd mybuild +$ cmake -DXO_CMAKE_CONFIG_EXECUTABLE=xo-cmake-config -DCMAKE_MODULE_PATH=$(xo-cmake-config --cmake-module-path) path/to/foo/source ``` From 2d94fd51bf98907192db09dce5048197d17f2d1f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 13 Jun 2024 15:20:36 -0400 Subject: [PATCH 1001/2524] xo-expression: initial commit (constant + primitive) --- .gitignore | 8 ++ CMakeLists.txt | 36 ++++++++ LICENSE | 29 +++++++ cmake/xo-bootstrap-macros.cmake | 35 ++++++++ cmake/xo_expressionConfig.cmake.in | 6 ++ example/CMakeLists.txt | 1 + example/ex1/CMakeLists.txt | 12 +++ example/ex1/ex1.cpp | 14 +++ include/xo/expression/Apply.hpp | 32 +++++++ include/xo/expression/Constant.hpp | 78 +++++++++++++++++ include/xo/expression/ConstantInterface.hpp | 41 +++++++++ include/xo/expression/Expression.hpp | 48 +++++++++++ include/xo/expression/Primitive.hpp | 89 ++++++++++++++++++++ include/xo/expression/PrimitiveInterface.hpp | 35 ++++++++ include/xo/expression/exprtype.hpp | 51 +++++++++++ src/expression/CMakeLists.txt | 14 +++ src/expression/Expression.cpp | 15 ++++ 17 files changed, 544 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100755 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_expressionConfig.cmake.in create mode 100644 example/CMakeLists.txt create mode 100644 example/ex1/CMakeLists.txt create mode 100644 example/ex1/ex1.cpp create mode 100644 include/xo/expression/Apply.hpp create mode 100644 include/xo/expression/Constant.hpp create mode 100644 include/xo/expression/ConstantInterface.hpp create mode 100644 include/xo/expression/Expression.hpp create mode 100644 include/xo/expression/Primitive.hpp create mode 100644 include/xo/expression/PrimitiveInterface.hpp create mode 100644 include/xo/expression/exprtype.hpp create mode 100644 src/expression/CMakeLists.txt create mode 100644 src/expression/Expression.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f3b23fc3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# emacs projectile config +.projectile +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..468778de --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,36 @@ +# expression/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_expression VERSION 0.1) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings + +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") # gcc-only! +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- + +# must complete definition of expression lib before configuring examples +add_subdirectory(src/expression) + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- + +add_subdirectory(example) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# docs targets depend on all the other library/utest targets +# +#add_subdirectory(docs) + +# end CMakeLists.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cae3cb5d --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2024 Roland Conybeare , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100755 index 00000000..aba31169 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/cmake/xo_expressionConfig.cmake.in b/cmake/xo_expressionConfig.cmake.in new file mode 100644 index 00000000..e7f7f1be --- /dev/null +++ b/cmake/xo_expressionConfig.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(reflect) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 00000000..4151ec21 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(ex1) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..d7e8b3b6 --- /dev/null +++ b/example/ex1/CMakeLists.txt @@ -0,0 +1,12 @@ +# xo-expression/example/ex1/CMakeLists.txt + +set(SELF_EXE xo_expression_ex1) +set(SELF_SRCS ex1.cpp) + +if (XO_ENABLE_EXAMPLES) + xo_add_executable(${SELF_EXE} ${SELF_SRCS}) + xo_self_dependency(${SELF_EXE} xo_expression) + xo_dependency(${SELF_EXE} refcnt) +endif() + +# end CMakeLists.txt diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp new file mode 100644 index 00000000..19dddc82 --- /dev/null +++ b/example/ex1/ex1.cpp @@ -0,0 +1,14 @@ +/** @file ex1.cpp **/ + +#include "xo/expression/Constant.hpp" +#include + +int +main() { + using xo::ast::make_constant; + + auto expr = make_constant(7); +} + + +/** end ex1.cpp **/ diff --git a/include/xo/expression/Apply.hpp b/include/xo/expression/Apply.hpp new file mode 100644 index 00000000..518415f0 --- /dev/null +++ b/include/xo/expression/Apply.hpp @@ -0,0 +1,32 @@ +/** @file Apply.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "Expression.hpp" + +//#include + +namespace xo { + namespace ast { + /** @class Apply + * @brief syntax for a function call. + * + * In general we don't know function to be invoked + * until runtime, depending on the nature of Expression. + * + * For first cut, we'll just handle the case of a Constant + * that refers to a known function + **/ + class Apply : public Expression { + ref::rp fn_; + std::vector> args_; + }; + } /*namespace ast*/ + +} /*namespace xo*/ + + +/** end Apply.hpp **/ diff --git a/include/xo/expression/Constant.hpp b/include/xo/expression/Constant.hpp new file mode 100644 index 00000000..2741881f --- /dev/null +++ b/include/xo/expression/Constant.hpp @@ -0,0 +1,78 @@ +/** @file Constant.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "ConstantInterface.hpp" +#include + +namespace xo { + namespace ast { + /** @class Constant + * @brief syntax for a literal constant. + * + * Require: + * 1. T must be a POD type (plain old data) + * We need this to be true so that we can generate + * code for constructing a T instance by memcopying + * @ref value_ + * + * @tp T type of captured literal. + **/ + template + class Constant : public ConstantInterface { + public: + using Reflect = xo::reflect::Reflect; + using TaggedPtr = xo::reflect::TaggedPtr; + using TypeDescr = xo::reflect::TypeDescr; + + public: + explicit Constant(const T & x) + : ConstantInterface(exprtype::constant), + value_td_{Reflect::require()}, + value_(x) + { + static_assert(std::is_standard_layout_v && std::is_trivial_v); + } + + const T & value() const { return value_; } + + // ----- ConstantInterface ----- + + virtual TypeDescr value_td() const override { return value_td_; } + virtual TaggedPtr value_tp() const override { + /* note: idk why, but need to spell this out in two steps with gcc 13.2 */ + const void * erased_cptr = &value_; + void * erased_ptr = const_cast(erased_cptr); + + return TaggedPtr(value_td_, erased_ptr); + } + + // ----- Expression ----- + + virtual void display(std::ostream & os) const override { + os << "short_name()) + << xtag("value", value_) + << ">"; + } + + private: + /** type description for T **/ + TypeDescr value_td_; + /** value of this constant **/ + T value_; + }; /*Constant*/ + + template + ref::rp>> + make_constant(const T & x) { + return new Constant(x); + } + + } /*namespace ast*/ +} /*namespace xo*/ + +/** end Constant.hpp **/ diff --git a/include/xo/expression/ConstantInterface.hpp b/include/xo/expression/ConstantInterface.hpp new file mode 100644 index 00000000..9bd38383 --- /dev/null +++ b/include/xo/expression/ConstantInterface.hpp @@ -0,0 +1,41 @@ +/** @file ConstantInterface.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "Expression.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include + +namespace xo { + namespace ast { + /** @class ConstantInterface + * @brief syntax for a literal constant. + **/ + class ConstantInterface : public Expression { + public: + using TaggedPtr = xo::reflect::TaggedPtr; + using TypeDescr = xo::reflect::TypeDescr; + + public: + /** @p extype sets expression-type; could be constant|primitive **/ + ConstantInterface(exprtype extype) : Expression{extype} {} + + /** downcast from Expression **/ + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + /** type description for representation of literal value **/ + virtual TypeDescr value_td() const = 0; + /** reflection-tagged pointer to literal value of this constant **/ + virtual TaggedPtr value_tp() const = 0; + }; /*ConstantInterface*/ + + } /*namespace ast*/ +} /*namespace xo*/ + +/** end ConstantInterface.hpp **/ diff --git a/include/xo/expression/Expression.hpp b/include/xo/expression/Expression.hpp new file mode 100644 index 00000000..cd23df53 --- /dev/null +++ b/include/xo/expression/Expression.hpp @@ -0,0 +1,48 @@ +/** @file Expression.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include "exprtype.hpp" + +namespace xo { + namespace ast { + /** @class Expression + * @brief abstract syntax tree for an EGAD program + * + * (Expression Graph with Automagic Derivation) + * + * Things you can do with an Expression: + * - evaluate it using an interpreter + * - execute it on a VM + * - compile using LLVM + * see xo-jit/ + **/ + class Expression : public ref::Refcount { + public: + explicit Expression(exprtype extype) : extype_{extype} {} + + exprtype extype() const { return extype_; } + + /** write human-readable representation to stream **/ + virtual void display(std::ostream & os) const = 0; + /** human-readable string representation **/ + virtual std::string display_string() const; + + private: + /** expression type (constant | apply | ..) for this expression **/ + exprtype extype_ = exprtype::invalid; + }; /*Expression*/ + + inline std::ostream & + operator<<(std::ostream & os, const Expression & x) { + x.display(os); + return os; + } + } /*namespace ast*/ +} /*namespace xo*/ + +/** end Expression.hpp **/ diff --git a/include/xo/expression/Primitive.hpp b/include/xo/expression/Primitive.hpp new file mode 100644 index 00000000..e0de42d7 --- /dev/null +++ b/include/xo/expression/Primitive.hpp @@ -0,0 +1,89 @@ +/** @file Primitive.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "PrimitiveInterface.hpp" +//#include + +namespace xo { + namespace ast { + /** @class Primitive + * @brief syntax for a constant that refers to a known function. + * + * Two cases here: + * 1. primitive refers to a function that is supported directly in llvm + * (e.g. floating-point addition) + * 2. primitive refers to a compiled (C/C++) function that we can invoke at runtime + * + * In any case, a primitive serves as both declaration and definition + * (May be possible to relax this to declaration-only using null value_ as sentinel..?) + **/ + template + class Primitive: public PrimitiveInterface { + public: + using Reflect = xo::reflect::Reflect; + using TaggedPtr = xo::reflect::TaggedPtr; + using TypeDescr = xo::reflect::TypeDescr; + + public: + Primitive(const std::string & name, + FunctionPointer fnptr) + : PrimitiveInterface(), + name_{name}, + value_td_{Reflect::require()}, + value_{fnptr} + {} + + FunctionPointer value() const { return value_; } + + // ----- PrimitiveInterface ----- + + virtual std::string const & name() const { return name_; } + + // ----- ConstantInterface ----- + + virtual TypeDescr value_td() const override { return value_td_; } + virtual TaggedPtr value_tp() const override { + /* note: idk why, but need to spell this out in two steps with gcc 13.2 */ + const void * erased_cptr = &value_; + void * erased_ptr = const_cast(erased_cptr); + + return TaggedPtr(value_td_, erased_ptr); + } + + // ----- Expression ----- + + virtual void display(std::ostream & os) const override { + os << "value_td()->short_name()) + << xtag("value", this->value()) + << ">"; + } + + private: + // from Expression: + // exprtype extype_ + + /** name of this primitive, e.g. '+', 'sqrt' **/ + std::string name_; + /** type description for FunctionPointer **/ + TypeDescr value_td_; + /** address of executable function **/ + FunctionPointer value_; + }; /*Primitive*/ + + /** adopt function @p x as a callable primitive function named @p name **/ + template + ref::rp> + make_primitive(const std::string & name, FunctionPointer x) { + return new Primitive(name, x); + } + } /*namespace ast*/ +} /*namespace xo*/ + + +/** end Primitive.hpp **/ diff --git a/include/xo/expression/PrimitiveInterface.hpp b/include/xo/expression/PrimitiveInterface.hpp new file mode 100644 index 00000000..54e51451 --- /dev/null +++ b/include/xo/expression/PrimitiveInterface.hpp @@ -0,0 +1,35 @@ +/** @file PrimitiveInterface.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "ConstantInterface.hpp" + +//#include + +#include +namespace xo { + namespace ast { + class PrimitiveInterface : public ConstantInterface { + public: + PrimitiveInterface() : ConstantInterface(exprtype::primitive) {} + + /** downcast from Expression **/ + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + virtual const std::string & name() const = 0; + + //virtual TypeDescr value_td() const override = 0; + //virtual TaggedPtr value_tp() const override = 0; + + private: + }; /*PrimitiveInterface*/ + } /*namespace ast*/ +} /*namespace xo*/ + + +/** end PrimitiveInterface.hpp **/ diff --git a/include/xo/expression/exprtype.hpp b/include/xo/expression/exprtype.hpp new file mode 100644 index 00000000..fe810bb7 --- /dev/null +++ b/include/xo/expression/exprtype.hpp @@ -0,0 +1,51 @@ +/** @file exprtype.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include + +namespace xo { + namespace ast { + /** @enum exprtype + * @brief enum to identify subclasses of xo::ast::Expression. + * + **/ + enum class exprtype { + /** sentinel value **/ + invalid = -1, + + /** literal constant. must satisfy both standard_layout_type + trivial **/ + constant, + /** a literal constant that refers to a linkable named function **/ + primitive, + /** function call **/ + apply, + + /** not an expression. comes last, counts entries **/ + n_expr + }; + + inline const char * + expr2str(exprtype x) + { + switch(x) { + case exprtype::invalid: return "?exprtype"; + case exprtype::constant: return "constant"; + case exprtype::primitive: return "primitive"; + case exprtype::apply: return "apply"; + default: break; + } + + return "???exprtype???"; + } + + /** @brief number of built-in expression types, repr convenient for array sizing **/ + static constexpr std::size_t n_exprtype = static_cast(exprtype::n_expr); + } /*namespace ast*/ +} /*namespace xo*/ + + +/** end exprtype.hpp **/ diff --git a/src/expression/CMakeLists.txt b/src/expression/CMakeLists.txt new file mode 100644 index 00000000..94e74d0c --- /dev/null +++ b/src/expression/CMakeLists.txt @@ -0,0 +1,14 @@ +# expression/CMakeLists.txt + +set(SELF_LIB xo_expression) +set(SELF_SRCS + Expression.cpp + #init_reflect.cpp +) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_dependency(${SELF_LIB} reflect) +#xo_dependency(${SELF_LIB} indentlog) +#xo_dependency(${SELF_LIB} subsys) + +# end CMakeLists.txt diff --git a/src/expression/Expression.cpp b/src/expression/Expression.cpp new file mode 100644 index 00000000..ecebe8b4 --- /dev/null +++ b/src/expression/Expression.cpp @@ -0,0 +1,15 @@ +/* @file Expression.cpp */ + +#include "Expression.hpp" + +namespace xo { + namespace ast { + std::string + Expression::display_string() const { + return tostr(*this); + } + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end Expression.cpp */ From 432c369a66e985732584b72ae0f881ca4ce60666 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 13 Jun 2024 15:21:17 -0400 Subject: [PATCH 1002/2524] xo-jit: initial commit (codegen constants + primitives, sort of) --- .gitignore | 8 +++ CMakeLists.txt | 36 ++++++++++++ LICENSE | 29 +++++++++ cmake/xo-bootstrap-macros.cmake | 35 +++++++++++ cmake/xo_jitConfig.cmake.in | 6 ++ example/CMakeLists.txt | 1 + example/ex1/CMakeLists.txt | 12 ++++ example/ex1/ex1.cpp | 56 ++++++++++++++++++ include/xo/jit/Jit.hpp | 76 ++++++++++++++++++++++++ src/jit/CMakeLists.txt | 35 +++++++++++ src/jit/Jit.cpp | 100 ++++++++++++++++++++++++++++++++ 11 files changed, 394 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100755 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_jitConfig.cmake.in create mode 100644 example/CMakeLists.txt create mode 100644 example/ex1/CMakeLists.txt create mode 100644 example/ex1/ex1.cpp create mode 100644 include/xo/jit/Jit.hpp create mode 100644 src/jit/CMakeLists.txt create mode 100644 src/jit/Jit.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f3b23fc3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# emacs projectile config +.projectile +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..094529c0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,36 @@ +# jit/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_jit VERSION 0.1) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings + +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") # gcc-only! +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- + +# must complete definition of jit lib before configuring examples +add_subdirectory(src/jit) + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- + +add_subdirectory(example) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# docs targets depend on all the other library/utest targets +# +#add_subdirectory(docs) + +# end CMakeLists.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cae3cb5d --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2024 Roland Conybeare , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100755 index 00000000..aba31169 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/cmake/xo_jitConfig.cmake.in b/cmake/xo_jitConfig.cmake.in new file mode 100644 index 00000000..26d2315a --- /dev/null +++ b/cmake/xo_jitConfig.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(xo_expression) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 00000000..4151ec21 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(ex1) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..9406c959 --- /dev/null +++ b/example/ex1/CMakeLists.txt @@ -0,0 +1,12 @@ +# xo-jit/example/ex1/CMakeLists.txt + +set(SELF_EXE xo_jit_ex1) +set(SELF_SRCS ex1.cpp) + +if (XO_ENABLE_EXAMPLES) + xo_add_executable(${SELF_EXE} ${SELF_SRCS}) + xo_self_dependency(${SELF_EXE} xo_jit) + #xo_dependency(${SELF_EXE} xo_expression) +endif() + +# end CMakeLists.txt diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp new file mode 100644 index 00000000..571b288f --- /dev/null +++ b/example/ex1/ex1.cpp @@ -0,0 +1,56 @@ +/** @file ex1.cpp **/ + +#include "xo/jit/Jit.hpp" +#include "xo/expression/Constant.hpp" +#include "xo/expression/Primitive.hpp" +#include + +int +main() { + using xo::jit::Jit; + using xo::ast::make_constant; + using xo::ast::make_primitive; + using xo::xtag; + using std::cerr; + using std::endl; + + //using xo::ast::make_constant; + + auto jit = Jit::make(); + + { + auto expr = make_constant(7.0); + + auto llvm_ircode = jit->codegen(expr); + + if (llvm_ircode) { + /* note: llvm:errs() is 'raw stderr stream' */ + cerr << "ex1 llvm_ircode:" << endl; + llvm_ircode->print(llvm::errs()); + cerr << endl; + } else { + cerr << "ex1: code generation failed" + << xtag("expr", expr) + << endl; + } + } + + { + auto expr = make_primitive("sqrt", ::sqrt); + + auto llvm_ircode = jit->codegen(expr); + + if (llvm_ircode) { + /* note: llvm:errs() is 'raw stderr stream' */ + cerr << "ex1 llvm_ircode:" << endl; + llvm_ircode->print(llvm::errs()); + cerr << endl; + } else { + cerr << "ex1: code generation failed" + << xtag("expr", expr) + << endl; + } + } +} + +/** end ex1.cpp **/ diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp new file mode 100644 index 00000000..f7adf0cc --- /dev/null +++ b/include/xo/jit/Jit.hpp @@ -0,0 +1,76 @@ +/** @file Jit.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +//#include + +#include "xo/refcnt/Refcounted.hpp" +#include "xo/expression/Expression.hpp" +#include "xo/expression/ConstantInterface.hpp" +#include "xo/expression/PrimitiveInterface.hpp" +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/PassManager.h" +#include "llvm/IR/Type.h" +#include "llvm/IR/Verifier.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/StandardInstrumentations.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Target/TargetMachine.h" +#include "llvm/Transforms/InstCombine/InstCombine.h" +#include "llvm/Transforms/Scalar.h" +#include "llvm/Transforms/Scalar/GVN.h" +#include "llvm/Transforms/Scalar/Reassociate.h" +#include "llvm/Transforms/Scalar/SimplifyCFG.h" + +namespace xo { + namespace jit { + /** @class Jit + * @brief just-in-time compiler for EGAD + * + * TODO: make module name a parameter? + **/ + class Jit : public ref::Refcount { + public: + using Expression = xo::ast::Expression; + //using ConstantInterface = xo::ast::ConstantInterface; + + public: + static ref::rp make() { return new Jit(); } + + llvm::Value * codegen_constant(ref::brw expr); + llvm::Function * codegen_primitive(ref::brw expr); + + llvm::Value * codegen(ref::brw expr); + + private: + Jit(); + + private: + /** owns + manages core "global" llvm data, + * including type- and constant- unique-ing tables. + * + * Not threadsafe, but ok to have multiple threads, + * each with its own LLVMContext + **/ + std::unique_ptr llvm_cx_; + /** a module (aka library) being prepared by llvm. + * - function names are unique within a module. + **/ + std::unique_ptr llvm_module_; + }; + } /*namespace jit*/ +} /*namespace xo*/ + + +/** end Jit.hpp **/ diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt new file mode 100644 index 00000000..abd3c903 --- /dev/null +++ b/src/jit/CMakeLists.txt @@ -0,0 +1,35 @@ +# jit/CMakeLists.txt + +set(SELF_LIB xo_jit) +set(SELF_SRCS + Jit.cpp +) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_dependency(${SELF_LIB} xo_expression) + +find_package(LLVM REQUIRED CONFIG) +message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") +message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") +message(STATUS "LLVM_DIR=${LLVM_DIR}") +message(STATUS "LLVM_DEFINITIONS=${LLVM_DEFINITIONS}") +message(STATUS "LLVM_INCLUDE_DIRS=[${LLVM_INCLUDE_DIRS}]") + +separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) + +message(STATUS "LLVM_DEFINITIONS_LIST=[${LLVM_DEFINITIONS_LIST}]") + +# Find the libraries that correspond to the LLVM components +execute_process( + COMMAND llvm-config --libs all + COMMAND tr -d '\n' + OUTPUT_VARIABLE LLVM_LIBS +) + +message(STATUS "LLVM_LIBS=[${LLVM_LIBS}]") + +target_include_directories(${SELF_LIB} PUBLIC ${LLVM_INCLUDE_DIRS}) +target_compile_definitions(${SELF_LIB} PUBLIC ${LLVM_DEFINITIONS_LIST}) +target_link_libraries(${SELF_LIB} PUBLIC ${LLVM_LIBS}) + +# end CMakeLists.txt diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp new file mode 100644 index 00000000..b5d3241e --- /dev/null +++ b/src/jit/Jit.cpp @@ -0,0 +1,100 @@ +/* @file Jit.cpp */ + +#include "Jit.hpp" + +namespace xo { + using xo::ast::exprtype; + using xo::ast::Expression; + using xo::ast::ConstantInterface; + using xo::ast::PrimitiveInterface; + using xo::reflect::TypeDescr; + + namespace jit { + Jit::Jit() + : llvm_cx_{std::make_unique()}, + llvm_module_{std::make_unique("xojit", *llvm_cx_)} + {} + + llvm::Value * + Jit::codegen_constant(ref::brw expr) + { + TypeDescr td = expr->value_td(); + + if (td->is_native()) { + return llvm::ConstantFP::get(*llvm_cx_, + llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (td->is_native()) { + return llvm::ConstantFP::get(*llvm_cx_, + llvm::APFloat(*(expr->value_tp().recover_native()))); + } + + return nullptr; + } + + llvm::Function * + Jit::codegen_primitive(ref::brw expr) + { + /** note: documentation (such as it is) for llvm::Function here: + * + * https://llvm.org/doxygen/classllvm_1_1Function.html + **/ + + auto * fn = llvm_module_->getFunction(expr->name()); + + if (fn) { + /** function with this name already known to llvm module; + * use that definition + * + * TODO: verify that signatures match! + **/ + return fn; + } + + /** establish prototype for this function **/ + + // PLACEHOLDER + // just make prototype for function :: double -> double + + std::vector double_v(1, llvm::Type::getDoubleTy(*llvm_cx_)); + + auto * llvm_fn_type = llvm::FunctionType::get(llvm::Type::getDoubleTy(*llvm_cx_), + double_v, + false /*!varargs*/); + + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + expr->name(), + llvm_module_.get()); + + // set names for arguments (for diagonostics?). Money-see-kaleidoscope-monkey-do here +#ifdef NOT_USING + for (auto & arg : fn->args()) + arg.setName(formalnameofthisarg); +#endif + + return fn; + } /*codegen_primitive*/ + + llvm::Value * + Jit::codegen(ref::brw expr) + { + switch(expr->extype()) { + case exprtype::constant: + return this->codegen_constant(ConstantInterface::from(expr)); + case exprtype::primitive: + return this->codegen_primitive(PrimitiveInterface::from(expr)); + case exprtype::apply: + break; + case exprtype::invalid: + case exprtype::n_expr: + return nullptr; + break; + } + + return nullptr; + } /*codegen*/ + + } /*namespace jit*/ +} /*namespace xo*/ + +/* end Jit.cpp */ From d6371ee3694a37aeea9f6b8f2e91d23e87d409f8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 13 Jun 2024 16:20:53 -0400 Subject: [PATCH 1003/2524] xo-expression: + Apply expressions --- include/xo/expression/Apply.hpp | 40 ++++++++++++++++---- include/xo/expression/Primitive.hpp | 2 + include/xo/expression/PrimitiveInterface.hpp | 1 + include/xo/expression/exprtype.hpp | 9 +++++ src/expression/Apply.cpp | 19 ++++++++++ src/expression/CMakeLists.txt | 1 + 6 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 src/expression/Apply.cpp diff --git a/include/xo/expression/Apply.hpp b/include/xo/expression/Apply.hpp index 518415f0..56f6b886 100644 --- a/include/xo/expression/Apply.hpp +++ b/include/xo/expression/Apply.hpp @@ -11,21 +11,47 @@ namespace xo { namespace ast { + /** @class Apply * @brief syntax for a function call. * * In general we don't know function to be invoked * until runtime, depending on the nature of Expression. - * - * For first cut, we'll just handle the case of a Constant - * that refers to a known function **/ class Apply : public Expression { - ref::rp fn_; - std::vector> args_; - }; - } /*namespace ast*/ + public: + Apply(const ref::rp & fn, + const std::vector> & argv) + : Expression(exprtype::apply), fn_{fn}, argv_(argv) + {} + /** downcast from Expression **/ + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + const ref::rp & fn() const { return fn_; } + const std::vector> & argv() const { return argv_; } + + virtual void display(std::ostream & os) const; + + private: + /** function to invoke **/ + ref::rp fn_; + /** argument expressions, in l-to-r order **/ + std::vector> argv_; + }; /*Apply*/ + + inline ref::rp + make_apply(const ref::rp & fn, + const ref::rp & arg1) { + std::vector> argv; + argv.push_back(arg1); + + return new Apply(fn, argv); + } /*make_apply*/ + + } /*namespace ast*/ } /*namespace xo*/ diff --git a/include/xo/expression/Primitive.hpp b/include/xo/expression/Primitive.hpp index e0de42d7..afae94a9 100644 --- a/include/xo/expression/Primitive.hpp +++ b/include/xo/expression/Primitive.hpp @@ -42,6 +42,8 @@ namespace xo { // ----- PrimitiveInterface ----- virtual std::string const & name() const { return name_; } + /** FIXME for now **/ + virtual int n_arg() const { return 1; } // ----- ConstantInterface ----- diff --git a/include/xo/expression/PrimitiveInterface.hpp b/include/xo/expression/PrimitiveInterface.hpp index 54e51451..c294a440 100644 --- a/include/xo/expression/PrimitiveInterface.hpp +++ b/include/xo/expression/PrimitiveInterface.hpp @@ -22,6 +22,7 @@ namespace xo { } virtual const std::string & name() const = 0; + virtual int n_arg() const = 0; //virtual TypeDescr value_td() const override = 0; //virtual TaggedPtr value_tp() const override = 0; diff --git a/include/xo/expression/exprtype.hpp b/include/xo/expression/exprtype.hpp index fe810bb7..1988f7e9 100644 --- a/include/xo/expression/exprtype.hpp +++ b/include/xo/expression/exprtype.hpp @@ -5,6 +5,7 @@ #pragma once +#include #include namespace xo { @@ -44,6 +45,14 @@ namespace xo { /** @brief number of built-in expression types, repr convenient for array sizing **/ static constexpr std::size_t n_exprtype = static_cast(exprtype::n_expr); + + inline std::ostream & + operator<<(std::ostream & os, + exprtype x) + { + os << expr2str(x); + return os; + } } /*namespace ast*/ } /*namespace xo*/ diff --git a/src/expression/Apply.cpp b/src/expression/Apply.cpp new file mode 100644 index 00000000..781ec170 --- /dev/null +++ b/src/expression/Apply.cpp @@ -0,0 +1,19 @@ +/* @file Apply.cpp */ + +#include "Apply.hpp" +#include "xo/indentlog/print/vector.hpp" + +namespace xo { + namespace ast { + void + Apply::display(std::ostream & os) const { + os << ""; + } + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end Apply.cpp */ diff --git a/src/expression/CMakeLists.txt b/src/expression/CMakeLists.txt index 94e74d0c..59e754a5 100644 --- a/src/expression/CMakeLists.txt +++ b/src/expression/CMakeLists.txt @@ -3,6 +3,7 @@ set(SELF_LIB xo_expression) set(SELF_SRCS Expression.cpp + Apply.cpp #init_reflect.cpp ) From 69dfaa931ab30cad66095107e6c1e70d6155f107 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 13 Jun 2024 16:21:19 -0400 Subject: [PATCH 1004/2524] xo-jit: + compile Apply expressions [wip] --- example/ex1/ex1.cpp | 24 ++++++++++++++++ include/xo/jit/Jit.hpp | 4 +++ src/jit/Jit.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 571b288f..bad239be 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -3,6 +3,7 @@ #include "xo/jit/Jit.hpp" #include "xo/expression/Constant.hpp" #include "xo/expression/Primitive.hpp" +#include "xo/expression/Apply.hpp" #include int @@ -10,6 +11,7 @@ main() { using xo::jit::Jit; using xo::ast::make_constant; using xo::ast::make_primitive; + using xo::ast::make_apply; using xo::xtag; using std::cerr; using std::endl; @@ -51,6 +53,28 @@ main() { << endl; } } + + { + /* (sqrt 2) */ + + auto fn = make_primitive("sqrt", ::sqrt); + auto arg = make_constant(2.0); + + auto call = make_apply(fn, arg); + + auto llvm_ircode = jit->codegen(call); + + if (llvm_ircode) { + /* note: llvm:errs() is 'raw stderr stream' */ + cerr << "ex1 llvm_ircode:" << endl; + llvm_ircode->print(llvm::errs()); + cerr << endl; + } else { + cerr << "ex1: code generation failed" + << xtag("expr", call) + << endl; + } + } } /** end ex1.cpp **/ diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index f7adf0cc..7e32fb26 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -11,6 +11,7 @@ #include "xo/expression/Expression.hpp" #include "xo/expression/ConstantInterface.hpp" #include "xo/expression/PrimitiveInterface.hpp" +#include "xo/expression/Apply.hpp" #include "llvm/ADT/APFloat.h" #include "llvm/ADT/STLExtras.h" #include "llvm/IR/BasicBlock.h" @@ -50,6 +51,7 @@ namespace xo { llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); + llvm::Value * codegen_apply(ref::brw expr); llvm::Value * codegen(ref::brw expr); @@ -64,6 +66,8 @@ namespace xo { * each with its own LLVMContext **/ std::unique_ptr llvm_cx_; + /** builder for intermediate-representation objects **/ + std::unique_ptr> llvm_ir_builder_; /** a module (aka library) being prepared by llvm. * - function names are unique within a module. **/ diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index b5d3241e..6bf39631 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -7,11 +7,15 @@ namespace xo { using xo::ast::Expression; using xo::ast::ConstantInterface; using xo::ast::PrimitiveInterface; + using xo::ast::Apply; using xo::reflect::TypeDescr; + using std::cerr; + using std::endl; namespace jit { Jit::Jit() : llvm_cx_{std::make_unique()}, + llvm_ir_builder_{std::make_unique>(*llvm_cx_)}, llvm_module_{std::make_unique("xojit", *llvm_cx_)} {} @@ -75,6 +79,60 @@ namespace xo { return fn; } /*codegen_primitive*/ + llvm::Value * + Jit::codegen_apply(ref::brw apply) + { + using std::cerr; + using std::endl; + + /* editorial: + * + * to handle (computed functions) properly, + * we will need a runtime representation for a 'primitive function pointer' + * + * For now, finesse by only handling PrimitiveInterface in function-callee position + */ + if (apply->fn()->extype() == exprtype::primitive) { + auto pm = PrimitiveInterface::from(apply->fn()); + auto * fn = this->codegen_primitive(pm); + +#ifdef NOT_USING_DEBUG + cerr << "Jit::codegen_apply: fn:" << endl; + fn->print(llvm::errs()); + cerr << endl; +#endif + + if (fn->arg_size() != apply->argv().size()) { + cerr << "Jit::codegen_apply: error: callee f expecting n1 args where n2 supplied" + << xtag("f", pm->name()) + << xtag("n1", pm->n_arg()) + << xtag("n2", apply->argv().size()) + << endl; + return nullptr; + } + + std::vector args; + args.reserve(apply->argv().size()); + + for (const auto & arg_expr : apply->argv()) { + auto * arg = this->codegen(arg_expr); + +#ifdef NOT_USING_DEBUG + cerr << "Jit::codegen_apply: arg:" << endl; + arg->print(llvm::errs()); + cerr << endl; +#endif + + args.push_back(arg); + } + + return llvm_ir_builder_->CreateCall(fn, args, "calltmp"); + } else { + cerr << "Jit::codegen_apply: error: only allowing call to known primitives at present" << endl; + return nullptr; + } + } /*codegen_apply*/ + llvm::Value * Jit::codegen(ref::brw expr) { @@ -84,6 +142,7 @@ namespace xo { case exprtype::primitive: return this->codegen_primitive(PrimitiveInterface::from(expr)); case exprtype::apply: + return this->codegen_apply(Apply::from(expr)); break; case exprtype::invalid: case exprtype::n_expr: @@ -91,6 +150,10 @@ namespace xo { break; } + cerr << "Jit::codegen: error: no handler for expression of type T" + << xtag("T", expr->extype()) + << endl; + return nullptr; } /*codegen*/ From 20b23b50fc7c584f386361e0d794b3a9bc5f3b80 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 13 Jun 2024 17:05:38 -0400 Subject: [PATCH 1005/2524] xo-expression: + Lambda (function definitions) --- include/xo/expression/Lambda.hpp | 44 ++++++++++++++++++++++++++++++ include/xo/expression/exprtype.hpp | 3 ++ src/expression/CMakeLists.txt | 1 + src/expression/Lambda.cpp | 19 +++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 include/xo/expression/Lambda.hpp create mode 100644 src/expression/Lambda.cpp diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp new file mode 100644 index 00000000..581ba274 --- /dev/null +++ b/include/xo/expression/Lambda.hpp @@ -0,0 +1,44 @@ +/** @file Lambda.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "Expression.hpp" +#include +#include +//#include + +namespace xo { + namespace ast { + /** @class Lambda + * @brief abstract syntax tree for a function definition + * + **/ + class Lambda : public Expression { + public: + /** @p argv Formal parameters, in left-to-right order + * @p body Expression for body of this function + **/ + Lambda(const std::vector & argv, + const ref::rp & body) + : Expression(exprtype::lambda), argv_{argv}, body_{body} {} + + const std::vector & argv() const { return argv_; } + const ref::rp & body() const { return body_; } + + // ----- Expression ----- + + virtual void display(std::ostream & os) const override; + + private: + /** formal argument names **/ + std::vector argv_; + /** function body **/ + ref::rp body_; + }; /*Lambda*/ + } /*namespace ast*/ +} /*namespace xo*/ + +/** end Lambda.hpp **/ diff --git a/include/xo/expression/exprtype.hpp b/include/xo/expression/exprtype.hpp index 1988f7e9..7afa8ff3 100644 --- a/include/xo/expression/exprtype.hpp +++ b/include/xo/expression/exprtype.hpp @@ -24,6 +24,8 @@ namespace xo { primitive, /** function call **/ apply, + /** function definition **/ + lambda, /** not an expression. comes last, counts entries **/ n_expr @@ -37,6 +39,7 @@ namespace xo { case exprtype::constant: return "constant"; case exprtype::primitive: return "primitive"; case exprtype::apply: return "apply"; + case exprtype::lambda: return "lambda"; default: break; } diff --git a/src/expression/CMakeLists.txt b/src/expression/CMakeLists.txt index 59e754a5..5db806b2 100644 --- a/src/expression/CMakeLists.txt +++ b/src/expression/CMakeLists.txt @@ -4,6 +4,7 @@ set(SELF_LIB xo_expression) set(SELF_SRCS Expression.cpp Apply.cpp + Lambda.cpp #init_reflect.cpp ) diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp new file mode 100644 index 00000000..d228bb2c --- /dev/null +++ b/src/expression/Lambda.cpp @@ -0,0 +1,19 @@ +/* @file Lambda.cpp */ + +#include "Lambda.hpp" +#include "xo/indentlog/print/vector.hpp" + +namespace xo { + namespace ast { + void + Lambda::display(std::ostream & os) const { + os << ""; + } /*display*/ + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end Lambda.cpp */ From 9d2b0f17f84975e51235d87514ad8c2d33e2724d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 13 Jun 2024 17:59:51 -0400 Subject: [PATCH 1006/2524] xo-expression: + Variable + apply/lambda fixes --- include/xo/expression/Apply.hpp | 2 +- include/xo/expression/Lambda.hpp | 27 +++++++++++++++++-- include/xo/expression/Variable.hpp | 42 ++++++++++++++++++++++++++++++ include/xo/expression/exprtype.hpp | 3 +++ src/expression/CMakeLists.txt | 1 + src/expression/Lambda.cpp | 1 + src/expression/Variable.cpp | 17 ++++++++++++ 7 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 include/xo/expression/Variable.hpp create mode 100644 src/expression/Variable.cpp diff --git a/include/xo/expression/Apply.hpp b/include/xo/expression/Apply.hpp index 56f6b886..1f2b9a8f 100644 --- a/include/xo/expression/Apply.hpp +++ b/include/xo/expression/Apply.hpp @@ -44,7 +44,7 @@ namespace xo { inline ref::rp make_apply(const ref::rp & fn, - const ref::rp & arg1) { + const ref::rp & arg1) { std::vector> argv; argv.push_back(arg1); diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index 581ba274..7f6434f8 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -21,10 +21,17 @@ namespace xo { /** @p argv Formal parameters, in left-to-right order * @p body Expression for body of this function **/ - Lambda(const std::vector & argv, + Lambda(const std::string & name, + const std::vector & argv, const ref::rp & body) - : Expression(exprtype::lambda), argv_{argv}, body_{body} {} + : Expression(exprtype::lambda), name_{name}, argv_{argv}, body_{body} {} + /** downcast from Expression **/ + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + const std::string & name() const { return name_; } const std::vector & argv() const { return argv_; } const ref::rp & body() const { return body_; } @@ -33,11 +40,27 @@ namespace xo { virtual void display(std::ostream & os) const override; private: + /** lambda name. Initially supporting only form like + * (define (foo x y z) + * (+ (* x x) (* y y) (* z z))) + * + * In any case need to supply names for distinct things-for-which-code-is-generated + * so that they can be linked etc. + **/ + std::string name_; /** formal argument names **/ std::vector argv_; /** function body **/ ref::rp body_; }; /*Lambda*/ + + inline ref::rp + make_lambda(const std::string & name, + const std::vector & argv, + const ref::rp & body) + { + return new Lambda(name, argv, body); + } } /*namespace ast*/ } /*namespace xo*/ diff --git a/include/xo/expression/Variable.hpp b/include/xo/expression/Variable.hpp new file mode 100644 index 00000000..fc565236 --- /dev/null +++ b/include/xo/expression/Variable.hpp @@ -0,0 +1,42 @@ +/** @file Variable.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "Expression.hpp" + +namespace xo { + namespace ast { + + /** @class Variable + * @brief syntax for a variable reference + **/ + class Variable : public Expression { + public: + Variable(const std::string & name) : Expression(exprtype::variable), name_{name} {} + + /** downcast from Expression **/ + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + const std::string & name() const { return name_; } + + virtual void display(std::ostream & os) const; + + private: + /** variable name **/ + std::string name_; + }; /*Variable*/ + + inline ref::rp + make_var(const std::string & name) { + return new Variable(name); + } + } /*namespace ast*/ +} /*namespace xo*/ + + +/** end Variable.hpp **/ diff --git a/include/xo/expression/exprtype.hpp b/include/xo/expression/exprtype.hpp index 7afa8ff3..91bcfbb5 100644 --- a/include/xo/expression/exprtype.hpp +++ b/include/xo/expression/exprtype.hpp @@ -26,6 +26,8 @@ namespace xo { apply, /** function definition **/ lambda, + /** variable reference **/ + variable, /** not an expression. comes last, counts entries **/ n_expr @@ -40,6 +42,7 @@ namespace xo { case exprtype::primitive: return "primitive"; case exprtype::apply: return "apply"; case exprtype::lambda: return "lambda"; + case exprtype::variable: return "variable"; default: break; } diff --git a/src/expression/CMakeLists.txt b/src/expression/CMakeLists.txt index 5db806b2..0ef351e1 100644 --- a/src/expression/CMakeLists.txt +++ b/src/expression/CMakeLists.txt @@ -5,6 +5,7 @@ set(SELF_SRCS Expression.cpp Apply.cpp Lambda.cpp + Variable.cpp #init_reflect.cpp ) diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index d228bb2c..87f70759 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -8,6 +8,7 @@ namespace xo { void Lambda::display(std::ostream & os) const { os << ""; diff --git a/src/expression/Variable.cpp b/src/expression/Variable.cpp new file mode 100644 index 00000000..c8ab19fc --- /dev/null +++ b/src/expression/Variable.cpp @@ -0,0 +1,17 @@ +/* @file Variable.cpp */ + +#include "Variable.hpp" + +namespace xo { + namespace ast { + void + Variable::display(std::ostream & os) const { + os << ""; + } /*display*/ + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end Variable.cpp */ From 731b91889c3acba36eedac2ebeaa7bdcead3d232 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 13 Jun 2024 18:00:16 -0400 Subject: [PATCH 1007/2524] xo-jit: handle variable refs + lambda defs --- example/ex1/ex1.cpp | 33 ++++++++++++++++ include/xo/jit/Jit.hpp | 11 ++++++ src/jit/Jit.cpp | 89 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 132 insertions(+), 1 deletion(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index bad239be..d86b77f4 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -4,6 +4,8 @@ #include "xo/expression/Constant.hpp" #include "xo/expression/Primitive.hpp" #include "xo/expression/Apply.hpp" +#include "xo/expression/Lambda.hpp" +#include "xo/expression/Variable.hpp" #include int @@ -12,6 +14,8 @@ main() { using xo::ast::make_constant; using xo::ast::make_primitive; using xo::ast::make_apply; + using xo::ast::make_var; + using xo::ast::make_lambda; using xo::xtag; using std::cerr; using std::endl; @@ -75,6 +79,35 @@ main() { << endl; } } + + { + /* (lambda (x) (sin (cos x))) */ + + auto sin = make_primitive("sin", ::sin); + auto cos = make_primitive("cos", ::cos); + + auto x_var = make_var("x"); + auto call1 = make_apply(cos, x_var); /* (cos x) */ + auto call2 = make_apply(sin, call1); /* (sin (cos x)) */ + + /* (define (lm_1 x) (sin (cos x))) */ + auto lambda = make_lambda("lm_1", + {"x"}, + call2); + + auto llvm_ircode = jit->codegen(lambda); + + if (llvm_ircode) { + /* note: llvm:errs() is 'raw stderr stream' */ + cerr << "ex1 llvm_ircode:" << endl; + llvm_ircode->print(llvm::errs()); + cerr << endl; + } else { + cerr << "ex1: code generation failed" + << xtag("expr", lambda) + << endl; + } + } } /** end ex1.cpp **/ diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 7e32fb26..4c1f1534 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -12,6 +12,8 @@ #include "xo/expression/ConstantInterface.hpp" #include "xo/expression/PrimitiveInterface.hpp" #include "xo/expression/Apply.hpp" +#include "xo/expression/Lambda.hpp" +#include "xo/expression/Variable.hpp" #include "llvm/ADT/APFloat.h" #include "llvm/ADT/STLExtras.h" #include "llvm/IR/BasicBlock.h" @@ -52,6 +54,8 @@ namespace xo { llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); llvm::Value * codegen_apply(ref::brw expr); + llvm::Function * codegen_lambda(ref::brw expr); + llvm::Value * codegen_variable(ref::brw var); llvm::Value * codegen(ref::brw expr); @@ -72,6 +76,13 @@ namespace xo { * - function names are unique within a module. **/ std::unique_ptr llvm_module_; + + /** map global names to functions/variables **/ + std::map> global_env_; + /** map variable names (formal parameters) to + * corresponding llvm interactor + **/ + std::map nested_env_; }; } /*namespace jit*/ } /*namespace xo*/ diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 6bf39631..d9fc0f61 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -7,6 +7,8 @@ namespace xo { using xo::ast::Expression; using xo::ast::ConstantInterface; using xo::ast::PrimitiveInterface; + using xo::ast::Lambda; + using xo::ast::Variable; using xo::ast::Apply; using xo::reflect::TypeDescr; using std::cerr; @@ -133,6 +135,88 @@ namespace xo { } } /*codegen_apply*/ + llvm::Function * + Jit::codegen_lambda(ref::brw lambda) + { + /* reminder! this is the *expression*, not the *closure* */ + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * fn = llvm_module_->getFunction(lambda->name()); + + if (fn) { + /** function with this name already defined?? **/ + return nullptr; + } + + /* establish prototype for this function */ + + // PLACEHOLDER + // just make prototype for function :: double -> double + + std::vector double_v(1, llvm::Type::getDoubleTy(*llvm_cx_)); + + auto * llvm_fn_type = llvm::FunctionType::get(llvm::Type::getDoubleTy(*llvm_cx_), + double_v, + false /*!varargs*/); + + /* create (initially empty) function */ + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + lambda->name(), + llvm_module_.get()); + /* also capture argument names */ + int i = 0; + for (auto & arg : fn->args()) + arg.setName(lambda->argv().at(i)); + + /* generate function body */ + + auto block = llvm::BasicBlock::Create(*llvm_cx_, "entry", fn); + + llvm_ir_builder_->SetInsertPoint(block); + + /* formal parameters need to appear in named_value_map_ */ + nested_env_.clear(); + for (auto & arg : fn->args()) + nested_env_[std::string(arg.getName())] = &arg; + + llvm::Value * retval = this->codegen(lambda->body()); + + if (retval) { + /* completes the function.. */ + llvm_ir_builder_->CreateRet(retval); + + /* validate! always validate! */ + llvm::verifyFunction(*fn); + + /* optimize! */ + // thefpm->run(*fn, *thefam); + + return fn; + } + + /* oops, something went wrong */ + fn->eraseFromParent(); + + return nullptr; + } /*codegen_lambda*/ + + llvm::Value * + Jit::codegen_variable(ref::brw var) + { + auto ix = nested_env_.find(var->name()); + + if (ix == nested_env_.end()) { + cerr << "Jit::codegen_variable: no binding for variable x" + << xtag("x", var->name()) + << endl; + } + + return ix->second; + } /*codegen_variable*/ + llvm::Value * Jit::codegen(ref::brw expr) { @@ -143,7 +227,10 @@ namespace xo { return this->codegen_primitive(PrimitiveInterface::from(expr)); case exprtype::apply: return this->codegen_apply(Apply::from(expr)); - break; + case exprtype::lambda: + return this->codegen_lambda(Lambda::from(expr)); + case exprtype::variable: + return this->codegen_variable(Variable::from(expr)); case exprtype::invalid: case exprtype::n_expr: return nullptr; From 9d31c6ad8776abbb162ba091837244ee1ca66ce2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 10:53:23 -0400 Subject: [PATCH 1008/2524] xo-reflect: cosmetic: code layout --- include/xo/reflect/vector/VectorTdx.hpp | 124 ++++++++++++------------ 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/include/xo/reflect/vector/VectorTdx.hpp b/include/xo/reflect/vector/VectorTdx.hpp index 944c451c..84e414b4 100644 --- a/include/xo/reflect/vector/VectorTdx.hpp +++ b/include/xo/reflect/vector/VectorTdx.hpp @@ -9,87 +9,87 @@ #include "xo/reflect/EstablishTypeDescr.hpp" namespace xo { - namespace reflect { - /* Extra type-associated information for a vector/array. - */ - class VectorTdx : public TypeDescrExtra { - public: - /* named ctor idiom. create new instance for a vector type */ - //static std::unique_ptr make(); + namespace reflect { + /* Extra type-associated information for a vector/array. + */ + class VectorTdx : public TypeDescrExtra { + public: + /* named ctor idiom. create new instance for a vector type */ + //static std::unique_ptr make(); - // ----- Inherited from TypeDescrExtra ----- + // ----- 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*/ + 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 ----- + // ----- StlVectorTdx ----- - /* require: - * - VectorT.size() - * - VectorT[int] :: lvalue - */ - template - class StlVectorTdx : public VectorTdx { - public: - using target_t = VectorT; + /* require: + * - VectorT.size() + * - VectorT[int] :: lvalue + */ + template + class StlVectorTdx : public VectorTdx { + public: + using target_t = VectorT; - static std::unique_ptr make() { - return std::unique_ptr(new StlVectorTdx()); - } /*make*/ + static std::unique_ptr make() { + return std::unique_ptr(new StlVectorTdx()); + } /*make*/ - virtual uint32_t n_child(void * object) const override { - target_t * vec = reinterpret_cast(object); + virtual uint32_t n_child(void * object) const override { + target_t * vec = reinterpret_cast(object); - return vec->size(); - } /*n_child*/ + return vec->size(); + } /*n_child*/ - virtual TaggedPtr child_tp(uint32_t i, void * object) const override { - target_t * vec = reinterpret_cast(object); + virtual TaggedPtr child_tp(uint32_t i, void * object) const override { + target_t * vec = reinterpret_cast(object); - return establish_most_derived_tp(&((*vec)[i])); - } /*child_tp*/ - }; /*StlVectorTdx*/ + return establish_most_derived_tp(&((*vec)[i])); + } /*child_tp*/ + }; /*StlVectorTdx*/ - // ----- std::array ----- + // ----- std::array ----- - /* coordinates with EstablishTdx>::make(), - * see [reflect/Reflect.hpp] - */ + /* coordinates with EstablishTdx>::make(), + * see [reflect/Reflect.hpp] + */ - template - using StdArrayTdx = StlVectorTdx>; + template + using StdArrayTdx = StlVectorTdx>; - // ----- std::vector ----- + // ----- std::vector ----- - /* coordinates with EstablishTdx>::make() - * see [reflect/Reflect.hpp] - */ - template - class StdVectorTdx : public VectorTdx { - public: - using target_t = std::vector; + /* coordinates with EstablishTdx>::make() + * see [reflect/Reflect.hpp] + */ + template + class StdVectorTdx : public VectorTdx { + public: + using target_t = std::vector; - static std::unique_ptr make() { - return std::unique_ptr(new StdVectorTdx()); - } /*make*/ + static std::unique_ptr make() { + return std::unique_ptr(new StdVectorTdx()); + } /*make*/ - virtual uint32_t n_child(void * object) const override { - target_t * vec = reinterpret_cast(object); + virtual uint32_t n_child(void * object) const override { + target_t * vec = reinterpret_cast(object); - return vec->size(); - } /*n_child*/ + return vec->size(); + } /*n_child*/ - virtual TaggedPtr child_tp(uint32_t i, void * object) const override { - target_t * vec = reinterpret_cast(object); + virtual TaggedPtr child_tp(uint32_t i, void * object) const override { + target_t * vec = reinterpret_cast(object); - return establish_most_derived_tp(&((*vec)[i])); - } - }; /*StdVectorTdx*/ - } /*namespace reflect*/ + return establish_most_derived_tp(&((*vec)[i])); + } + }; /*StdVectorTdx*/ + } /*namespace reflect*/ } /*namespace xo*/ From 9db09697337595aaad6b006583dadf31e9a3ce44 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 10:53:35 -0400 Subject: [PATCH 1009/2524] xo-reflect: cosmetic: code layout --- include/xo/reflect/struct/StructTdx.hpp | 136 ++++++++++++------------ 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/include/xo/reflect/struct/StructTdx.hpp b/include/xo/reflect/struct/StructTdx.hpp index 594de2d5..15eebe9b 100644 --- a/include/xo/reflect/struct/StructTdx.hpp +++ b/include/xo/reflect/struct/StructTdx.hpp @@ -11,85 +11,85 @@ #include namespace xo { - namespace reflect { - /* Extra type-associated information for a struct/class. - * We use this to preserve information about memory layout - * at runtime - */ - class StructTdx : public TypeDescrExtra { - public: - /* named ctor idiom. create new instance for struct with given member list - * - * to_self_tp. use this function to support .most_derived_self_tp() - */ - static std::unique_ptr make(std::vector member_v, - bool have_to_self_tp, - std::function to_self_tp); + namespace reflect { + /* Extra type-associated information for a struct/class. + * We use this to preserve information about memory layout + * at runtime + */ + class StructTdx : public TypeDescrExtra { + public: + /* named ctor idiom. create new instance for struct with given member list + * + * to_self_tp. use this function to support .most_derived_self_tp() + */ + static std::unique_ptr make(std::vector member_v, + bool have_to_self_tp, + std::function to_self_tp); - /* specialization for std::pair - * coordinates with [reflect/Reflect.hpp] - */ - template - static std::unique_ptr pair() { - using struct_t = std::pair; + /* specialization for std::pair + * coordinates with [reflect/Reflect.hpp] + */ + template + static std::unique_ptr pair() { + using struct_t = std::pair; - std::vector mv; - { - auto lhs_access - (GeneralStructMemberAccessor::make - (&struct_t::first)); + std::vector mv; + { + auto lhs_access + (GeneralStructMemberAccessor::make + (&struct_t::first)); - mv.push_back(StructMember("first", std::move(lhs_access))); - } - { - auto rhs_access - (GeneralStructMemberAccessor::make - (&struct_t::second)); + mv.push_back(StructMember("first", std::move(lhs_access))); + } + { + auto rhs_access + (GeneralStructMemberAccessor::make + (&struct_t::second)); - mv.push_back(StructMember("second", std::move(rhs_access))); - } + mv.push_back(StructMember("second", std::move(rhs_access))); + } - std::function null_to_self_tp; + std::function null_to_self_tp; - return make(std::move(mv), - false /*!have_to_self_tp*/, - null_to_self_tp); - } /*pair*/ + return make(std::move(mv), + false /*!have_to_self_tp*/, + null_to_self_tp); + } /*pair*/ - // ----- Inherited from TypeDescrExtra ----- + // ----- 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; + virtual Metatype metatype() const override { return Metatype::mt_struct; } + virtual TaggedPtr most_derived_self_tp(TypeDescrBase const * object_td, + void * object) const override { + if (this->have_to_self_tp_) { + return this->to_self_tp_(object); + } else { + return TypeDescrExtra::most_derived_self_tp(object_td, object); + } + } + virtual uint32_t n_child(void * /*object*/) const override { return this->member_v_.size(); } + virtual TaggedPtr child_tp(uint32_t i, void * object) const override; + virtual std::string const & struct_member_name(uint32_t i) const override; + virtual StructMember const * struct_member(uint32_t i) const override; - private: - StructTdx(std::vector member_v, - bool have_to_self_tp, - std::function to_self_tp) - : member_v_{std::move(member_v)}, - have_to_self_tp_{have_to_self_tp}, - to_self_tp_{std::move(to_self_tp)} {} + private: + StructTdx(std::vector member_v, + bool have_to_self_tp, + std::function to_self_tp) + : member_v_{std::move(member_v)}, + have_to_self_tp_{have_to_self_tp}, + to_self_tp_{std::move(to_self_tp)} {} - private: - /* per-instance-variable reflection details */ - std::vector member_v_; - /* true if .to_self_tp() is defined */ - bool have_to_self_tp_ = false; - /* get TaggedPtr for most-derived subtype of supplied T-instance */ - std::function to_self_tp_; - }; /*StructTdx*/ + private: + /* per-instance-variable reflection details */ + std::vector member_v_; + /* true if .to_self_tp() is defined */ + bool have_to_self_tp_ = false; + /* get TaggedPtr for most-derived subtype of supplied T-instance */ + std::function to_self_tp_; + }; /*StructTdx*/ - } /*namespace reflect*/ + } /*namespace reflect*/ } /*namespace xo*/ /* end StructTdx.hpp */ From 0fe2b5107698d1b6d5dbe659977a0dbd6465f599 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 10:53:51 -0400 Subject: [PATCH 1010/2524] xo-reflect: cosmetic: code layout --- include/xo/reflect/SelfTagging.hpp | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/include/xo/reflect/SelfTagging.hpp b/include/xo/reflect/SelfTagging.hpp index b41d4ce3..12eb279f 100644 --- a/include/xo/reflect/SelfTagging.hpp +++ b/include/xo/reflect/SelfTagging.hpp @@ -10,22 +10,22 @@ #include "TaggedRcptr.hpp" namespace xo { - namespace reflect { - /* a self-tagging object uses reflection to preserve type information - * until runtime. Can use the reflected information to traverse - * object representation (e.g. for printing / serialization) - * without repetitive/bulky boilerplate. - * - * For pybind11 need to have concrete (non-template) apis, - * helpful to have various classes inherit SelfTagging - * - * For example see [printjson/PrintJson.hpp] - */ - class SelfTagging : public ref::Refcount { - public: - virtual TaggedRcptr self_tp() = 0; - }; /*SelfTagging*/ - } /*namespace reflect*/ + namespace 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 */ From fba8ed9c895d473cc249a3e90b38a5743108e87c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 10:54:14 -0400 Subject: [PATCH 1011/2524] xo-reflect: github: fix title for cmake CI --- .github/workflows/cmake-single-platform.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 77554e15..49c1a81c 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -1,6 +1,4 @@ -# This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. -# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml -name: CMake on a single platform +name: xo-reflect ubuntu build on: push: From 188825d449dff635b2d30069bc504b9adf19f050 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 10:54:35 -0400 Subject: [PATCH 1012/2524] xo-reflect: cosmetic: code layout --- src/reflect/atomic/AtomicTdx.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/reflect/atomic/AtomicTdx.cpp b/src/reflect/atomic/AtomicTdx.cpp index 42ca9e2f..2e04ab4d 100644 --- a/src/reflect/atomic/AtomicTdx.cpp +++ b/src/reflect/atomic/AtomicTdx.cpp @@ -4,21 +4,22 @@ #include "TaggedPtr.hpp" namespace xo { - namespace reflect { - std::unique_ptr AtomicTdx::make() { - return std::unique_ptr(new AtomicTdx()); - } /*make*/ + namespace reflect { + std::unique_ptr + AtomicTdx::make() { + return std::unique_ptr(new AtomicTdx()); + } /*make*/ - TaggedPtr - AtomicTdx::child_tp(uint32_t /*i*/, void * /*object*/) const { - return TaggedPtr::universal_null(); - } /*child_tp*/ + 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*/ + 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 */ From aa55e20e1279800980bba44e736c2a4af0cc560a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 10:55:08 -0400 Subject: [PATCH 1013/2524] xo-reflect: + infra for reflecting functions --- include/xo/reflect/Metatype.hpp | 57 +++++++++++---------- include/xo/reflect/function/FunctionTdx.hpp | 49 ++++++++++++++++++ src/reflect/function/FunctionTdx.cpp | 37 +++++++++++++ 3 files changed, 116 insertions(+), 27 deletions(-) create mode 100644 include/xo/reflect/function/FunctionTdx.hpp create mode 100644 src/reflect/function/FunctionTdx.cpp diff --git a/include/xo/reflect/Metatype.hpp b/include/xo/reflect/Metatype.hpp index 23f3fd10..549d098e 100644 --- a/include/xo/reflect/Metatype.hpp +++ b/include/xo/reflect/Metatype.hpp @@ -5,34 +5,37 @@ #include namespace xo { - namespace reflect { - enum class Metatype { mt_invalid, mt_atomic, mt_pointer, mt_vector, mt_struct }; + namespace reflect { + enum class Metatype { mt_invalid, mt_atomic, mt_pointer, mt_vector, mt_struct, mt_function }; - inline std::ostream & operator<<(std::ostream & os, - Metatype x) { - switch(x) { - case Metatype::mt_invalid: - os << "invalid!"; - break; - case Metatype::mt_atomic: - os << "atomic"; - break; - case Metatype::mt_pointer: - os << "pointer"; - break; - case Metatype::mt_vector: - os << "vector"; - break; - case Metatype::mt_struct: - os << "struct"; - break; - default: - os << "???"; - } - return os; - } /*operator<<*/ - - } /*namespace reflect*/ + inline std::ostream & operator<<(std::ostream & os, + Metatype x) { + switch(x) { + case Metatype::mt_invalid: + os << "invalid!"; + break; + case Metatype::mt_atomic: + os << "atomic"; + break; + case Metatype::mt_pointer: + os << "pointer"; + break; + case Metatype::mt_vector: + os << "vector"; + break; + case Metatype::mt_struct: + os << "struct"; + break; + case Metatype::mt_function: + os << "function"; + break; + default: + os << "???"; + } + return os; + } /*operator<<*/ + + } /*namespace reflect*/ } /*namespace xo*/ /* end Metatype.hpp */ diff --git a/include/xo/reflect/function/FunctionTdx.hpp b/include/xo/reflect/function/FunctionTdx.hpp new file mode 100644 index 00000000..1558328d --- /dev/null +++ b/include/xo/reflect/function/FunctionTdx.hpp @@ -0,0 +1,49 @@ +/** @file FunctionTdx.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/reflect/TypeDescrExtra.hpp" +#include "xo/reflect/EstablishTypeDescr.hpp" + +namespace xo { + namespace reflect { + /** Additional type-associated information for a function/procedure **/ + class FunctionTdx : public TypeDescrExtra { + public: + virtual ~FunctionTdx() = default; + + /** create instance. Will be invoked exactly once for each reflected function type **/ + static std::unique_ptr make_function(TypeDescr retval_td, + std::vector arg_td_v); + + + + // ----- Inherited from TypeDescrExtra ----- + + virtual Metatype metatype() const override { return Metatype::mt_function; } + virtual uint32_t n_child(void * /*object*/) const override { return 0; } + virtual TaggedPtr child_tp(uint32_t i, void * object) const override; + const std::string & struct_member_name(uint32_t i) const override; + + virtual uint32_t n_fn_arg() const override { return arg_td_v_.size(); } + virtual TypeDescr fn_retval() const override { return retval_td_; } + virtual TypeDescr fn_arg(uint32_t i) const override { return arg_td_v_[i]; } + + private: + FunctionTdx(TypeDescr retval_td, + std::vector arg_td_v); + + private: + /** function return value **/ + TypeDescr retval_td_; + /** function arguments, in positional order **/ + std::vector arg_td_v_; + }; /*FunctionTdx*/ + } /*namespace reflect*/ +} /*namespace xo*/ + + +/** end FunctionTdx.hpp **/ diff --git a/src/reflect/function/FunctionTdx.cpp b/src/reflect/function/FunctionTdx.cpp new file mode 100644 index 00000000..1b715fcd --- /dev/null +++ b/src/reflect/function/FunctionTdx.cpp @@ -0,0 +1,37 @@ +/* @file FunctionTdx.cpp */ + +#include "function/FunctionTdx.hpp" +#include "TaggedPtr.hpp" + +namespace xo { + namespace reflect { + /** create instance. Will be invoked exactly once for each reflected function type **/ + std::unique_ptr + FunctionTdx::make_function(TypeDescr retval_td, + std::vector arg_td_v) + { + return std::unique_ptr(new FunctionTdx(retval_td, std::move(arg_td_v))); + } + + FunctionTdx::FunctionTdx(TypeDescr retval_td, + std::vector arg_td_v) + : retval_td_{retval_td}, + arg_td_v_{std::move(arg_td_v)} + {} + + TaggedPtr + FunctionTdx::child_tp(uint32_t /*i*/, void * /*object*/) const + { + return TaggedPtr::universal_null(); + } + + const std::string & + FunctionTdx::struct_member_name(uint32_t i) const + { + return TypeDescrExtra::struct_member_name(i); + } + } /*namespace reflect*/ +} /*namespace xo*/ + + +/* end FunctionTdx.cpp */ From ec365d8b6f6f68e729c8393ec8d64685ee1f050c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 10:55:36 -0400 Subject: [PATCH 1014/2524] xo-reflect: build: ++ FunctionTdx.cpp --- src/reflect/CMakeLists.txt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/reflect/CMakeLists.txt b/src/reflect/CMakeLists.txt index 397876fa..87417066 100644 --- a/src/reflect/CMakeLists.txt +++ b/src/reflect/CMakeLists.txt @@ -7,20 +7,13 @@ set(SELF_SRCS pointer/PointerTdx.cpp vector/VectorTdx.cpp struct/StructTdx.cpp struct/StructMember.cpp + function/FunctionTdx.cpp init_reflect.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) - -# ---------------------------------------------------------------- -# dependencies: indentlog, ... - xo_dependency(${SELF_LIB} refcnt) xo_dependency(${SELF_LIB} indentlog) xo_dependency(${SELF_LIB} subsys) - -# ---------------------------------------------------------------- -# 3rd party dependency: boost: - #xo_boost_dependency(${SELF_LIB}) # end CMakeLists.txt From ef191b136ebf5cee087ee19855cbfbbf4460253a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 10:56:35 -0400 Subject: [PATCH 1015/2524] xo-reflect: automatically reflecct function pointers --- include/xo/reflect/EstablishTypeDescr.hpp | 89 ++++++++++-------- include/xo/reflect/Reflect.hpp | 84 +++++++++++++++-- include/xo/reflect/TaggedPtr.hpp | 12 ++- include/xo/reflect/TypeDescr.hpp | 11 +++ include/xo/reflect/TypeDescrExtra.hpp | 108 ++++++++++++---------- 5 files changed, 206 insertions(+), 98 deletions(-) diff --git a/include/xo/reflect/EstablishTypeDescr.hpp b/include/xo/reflect/EstablishTypeDescr.hpp index 32e546d1..13c80999 100644 --- a/include/xo/reflect/EstablishTypeDescr.hpp +++ b/include/xo/reflect/EstablishTypeDescr.hpp @@ -9,53 +9,66 @@ #include "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 - */ + namespace reflect { + /** @class EstablishTypeDescr + * @brief class to establish globally-unique TypeDescr object for a type T + * + * We don't require the full definition of T to use EstablishTypeDescr::establish(). + * In particular, a forward declaration is sufficient. + * + * Additional information (that depends on full definition) may be attached later, + * by assigning (once-only) to @ref TypeDescr::tdextra_ + * + * @note Application code will use @ref Reflect::require; that in turn relies on the + * template @ref EstablishTdx to leverage template pattern-matching for + * recurring patterns. + **/ + class EstablishTypeDescr { + public: + /* implementation method; expect this to be used only within reflect/ library. + * avoids some otherwise-cyclic #include paths + * between specialized headers such as vector/VectorTdx.hpp and this + * EstablishTypeDescr.hpp + */ #ifdef OBSOLETE - template - static TaggedPtr establish_tp(T * x) { return TaggedPtr(establish(), x); } + template + static TaggedPtr establish_tp(T * x) { return TaggedPtr(establish(), x); } #endif - template - static TaggedPtr establish_most_derived_tp(T * x) { return establish()->most_derived_self_tp(x); } + template + static TaggedPtr establish_most_derived_tp(T * x) { return establish()->most_derived_self_tp(x); } - template - static TypeDescrW establish() { - TypeDescrW td = TypeDescrBase::require(&typeid(T), - type_name(), - nullptr); + template + static TypeDescrW establish() { + TypeDescrW td = TypeDescrBase::require(&typeid(T), + type_name(), + nullptr); #ifdef NOT_USING - std::function to_self_tp; + std::function to_self_tp; - if (std::is_base_of_v) { - /* T is a descendant of SelfTagging (or T = SelfTagging); - * use SelfTagging.self_tp() - */ - to_self_tp = [](void * x) { return reinterpret_cast(x)->self_tp(); }; - } else { - /* T is not a descendant of SelfTagging. - * want to return - */ - to_self_tp = [td](void * x) { return TaggedPtr(td, x); }; - } + if (std::is_base_of_v) { + /* T is a descendant of SelfTagging (or T = SelfTagging); + * use SelfTagging.self_tp() + */ + to_self_tp = [](void * x) { return reinterpret_cast(x)->self_tp(); }; + } else { + /* T is not a descendant of SelfTagging. + * want to return + */ + to_self_tp = [td](void * x) { return TaggedPtr(td, x); }; + } - td->assign_to_self_tp(to_self_tp); + td->assign_to_self_tp(to_self_tp); #endif - return td; - } - }; /*EstablishTypeDescr*/ + return td; + } + }; /*EstablishTypeDescr*/ - template - inline TaggedPtr establish_most_derived_tp(T * x) { - return EstablishTypeDescr::establish_most_derived_tp(x); - } - } /*namespace reflect*/ + template + inline TaggedPtr establish_most_derived_tp(T * x) { + return EstablishTypeDescr::establish_most_derived_tp(x); + } + } /*namespace reflect*/ } /*namespace xo*/ diff --git a/include/xo/reflect/Reflect.hpp b/include/xo/reflect/Reflect.hpp index 89a75374..bba62ae1 100644 --- a/include/xo/reflect/Reflect.hpp +++ b/include/xo/reflect/Reflect.hpp @@ -11,6 +11,7 @@ #include "pointer/PointerTdx.hpp" #include "vector/VectorTdx.hpp" #include "struct/StructTdx.hpp" +#include "function/FunctionTdx.hpp" #include "xo/refcnt/Refcounted.hpp" #include #include @@ -21,44 +22,69 @@ namespace xo { template class EstablishTdx { public: + /** Create auxiliary reflection info for type @tparam T, + * once full definition is available. + * + * This includes: + * - metatype + * - component structure (types for navigable component objects) + * + **/ static std::unique_ptr make() { return AtomicTdx::make(); } - }; /*EstablishTdx*/ + }; // ----- xo::ref::rp ----- - /* definition provide after decl for Reflect {} below */ template class EstablishTdx> { public: + /* definition provide after decl for Reflect {} below */ static std::unique_ptr make(); - }; /*EstablishTdx*/ + }; // ----- std::array ----- - /* definition provide after decl for Reflect {} below */ template class EstablishTdx> { public: + /* definition provide after decl for Reflect {} below */ static std::unique_ptr make(); - }; /*EstablishTdx*/ + }; // ----- std::vector ----- - /* definition provide after decl for Reflect {} below */ template class EstablishTdx> { public: + /* definition provide after decl for Reflect {} below */ static std::unique_ptr make(); - }; /*EstablishTdx*/ + }; // ----- std::pair ----- - /* definition provide after decl for Reflect {} below */ template class EstablishTdx> { public: + /* definition provide after decl for Reflect {} below */ static std::unique_ptr make(); - }; /*EstablishTdx*/ + }; + + // ----- Retval (*)(A1 .. An) ----- + + template + class EstablishTdx { + public: + /* definition provided after decl for Reflect {} below */ + static std::unique_ptr make(); + }; + + // ----- Retval (*)() ----- + + template + class EstablishTdx { + /* definition provided after decl for Reflect {} below */ + static std::unique_ptr make(); + }; // ----- MakeTagged ----- @@ -229,6 +255,46 @@ namespace xo { return StructTdx::pair(); } /*make*/ + + // ----- Retval (*) (A1 .. An) ----- + + namespace detail { + /** @class AssembleArgv + * @brief create vector of complete TypeDescr objects comprising all template arguments + * + * Use: + * std::vector v; + * AssembleArgv::append_argv(&v); + * // do something with v + **/ + template + struct AssembleArgv; + + template <> + struct AssembleArgv<> { + static void append_argv(std::vector * p_v) {} + }; + + template + struct AssembleArgv { + static void append_argv(std::vector * p_v) { + p_v->push_back(Reflect::require()); + AssembleArgv::append_argv(p_v); + } + }; + } /*detail*/ + + /* declared above before + * class Reflect { ... } + */ + template + std::unique_ptr + EstablishTdx::make() { + std::vector argv; + detail::AssembleArgv::append_argv(&argv); + + return FunctionTdx::make_function(Reflect::require(), std::move(argv)); + } } /*namespace reflect*/ } /*namespace xo*/ diff --git a/include/xo/reflect/TaggedPtr.hpp b/include/xo/reflect/TaggedPtr.hpp index 7bb520aa..dce3fa06 100644 --- a/include/xo/reflect/TaggedPtr.hpp +++ b/include/xo/reflect/TaggedPtr.hpp @@ -59,9 +59,10 @@ namespace xo { void assign_address(void * x) { address_ = x; } bool is_universal_null() const { return (td_ == nullptr) && (address_ == nullptr); } + bool is_pointer() const { return td_ && td_->is_pointer(); } bool is_vector() const { return td_ && td_->is_vector(); } bool is_struct() const { return td_ && td_->is_struct(); } - + bool is_function() const { return td_ && td_->is_function(); } /* returns pointer-to-T, if in fact this tagged pointer is understood * to refer to a T-instance; otherwise nullptr @@ -71,11 +72,16 @@ namespace xo { 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*/ + } + + /* if reflected function (.is_function() = true): + * number of arguments to that function + */ + uint32_t n_fn_arg() const { return this->td_->n_fn_arg(); } /* require: * - .is_struct() is true diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index 94aa3aa3..c50a6d8d 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -179,8 +179,10 @@ namespace xo { } } /*recover_native*/ + bool is_pointer() const { return this->tdextra_->is_pointer(); } bool is_vector() const { return this->tdextra_->is_vector(); } bool is_struct() const { return this->tdextra_->is_struct(); } + bool is_function() const { return this->tdextra_->is_function(); } /* given a T-instance object, return tagged pointer with T replaced * by the most-derived-subtype of T to which *object belongs. @@ -203,6 +205,7 @@ namespace xo { 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: @@ -216,6 +219,14 @@ namespace xo { return *sm; } /*struct_member*/ + uint32_t n_fn_arg() const { return this->tdextra_->n_fn_arg(); } + + /* require: + * - .is_function() = true + */ + TypeDescr fn_retval() const { return this->tdextra_->fn_retval(); } + TypeDescr fn_arg(uint32_t i) const { return this->tdextra_->fn_arg(i); } + void display(std::ostream & os) const; std::string display_string() const; diff --git a/include/xo/reflect/TypeDescrExtra.hpp b/include/xo/reflect/TypeDescrExtra.hpp index 824cd5d4..54db3025 100644 --- a/include/xo/reflect/TypeDescrExtra.hpp +++ b/include/xo/reflect/TypeDescrExtra.hpp @@ -8,58 +8,70 @@ #include namespace xo { - namespace reflect { - /* forward-declaring here. see [reflect/struct/StructMember.hpp] */ - class StructMember; - class TypeDescrBase; - class TaggedPtr; + namespace reflect { + /* forward-declaring here. see [reflect/struct/StructMember.hpp] */ + class StructMember; + class TypeDescrBase; + class TaggedPtr; - /* information associated with a c++ type. - * distinct from TypeDescrImpl: - * 1. want to use reflection to support for runtime polymorphism over similar but - * not directly-related types: for example - * std::vector - * and - * std::list - * are both ordered collections - * 2. some information can't be universally established via template-fu, - * for example struct member names - * 3. descriptions for recursive types require 2-stage construction - * - * A TypeDescrImpl instance will contain a pointer to a suitable - * TypeDescrExtra instance. - * - * The single TypeDescrImpl instance for some type T can be established - * automatically, see Reflect::require(). - * - * A specific TypeDescrExtra instance may be attached in a non-automated way - * later - */ - class TypeDescrExtra { - public: - using uint32_t = std::uint32_t; + /* information associated with a c++ type. + * distinct from TypeDescrImpl: + * 1. want to use reflection to support for runtime polymorphism over similar but + * not directly-related types: for example + * std::vector + * and + * std::list + * are both ordered collections + * 2. some information can't be universally established via template-fu, + * for example struct member names + * 3. descriptions for recursive types require 2-stage construction + * + * A TypeDescrImpl instance will contain a pointer to a suitable + * TypeDescrExtra instance. + * + * The single TypeDescrImpl instance for some type T can be established + * automatically, see Reflect::require(). + * + * A specific TypeDescrExtra instance may be attached in a non-automated way + * later + */ + class TypeDescrExtra { + public: + using uint32_t = std::uint32_t; - public: - virtual ~TypeDescrExtra() = default; + public: + virtual ~TypeDescrExtra() = default; - bool is_vector() const { return this->metatype() == Metatype::mt_vector; } - bool is_struct() const { return this->metatype() == Metatype::mt_struct; } + bool is_pointer() const { return this->metatype() == Metatype::mt_pointer; } + bool is_vector() const { return this->metatype() == Metatype::mt_vector; } + bool is_struct() const { return this->metatype() == Metatype::mt_struct; } + bool is_function() const { return this->metatype() == Metatype::mt_function; } - virtual Metatype metatype() const = 0; - /* given a T-instance, report most-derived subtype of T to which *object belongs. - * this works only for types that are derived from reflect::SelfTagging. - */ - virtual TaggedPtr most_derived_self_tp(TypeDescrBase const * object_td, void * object) const; - virtual uint32_t n_child(void * object) const = 0; - 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*/ + 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; + + // methods for working with reflected functions/methods + + /** number of arguments to function-like value + * + * @pre @ref TypeDescrExtra::is_function() is true + **/ + virtual uint32_t n_fn_arg() const { return 0; } + virtual const TypeDescrBase * fn_retval() const { return nullptr; } + virtual const TypeDescrBase * fn_arg(uint32_t /*i_arg*/) const { return nullptr; } + }; /*TypeDescrExtra*/ + } /*namespace reflect*/ } /*namespace xo*/ /* end TypeDescrExtra.hpp */ From fb8f4fdec29cc102767cc77905a6162cb1730505 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 10:56:50 -0400 Subject: [PATCH 1016/2524] xo-reflect: utest for function reflection --- CMakeLists.txt | 10 +--------- utest/CMakeLists.txt | 7 ++++++- utest/FunctionTdx.test.cpp | 40 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 utest/FunctionTdx.test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 050bc94c..c2e8b74d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,15 +7,7 @@ project(reflect VERSION 0.1) include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) -xo_cxx_toplevel_options2() - -# ---------------------------------------------------------------- -# cmake -DCMAKE_BUILD_TYPE=coverage -xo_toplevel_coverage_config2() - -# ---------------------------------------------------------------- -# cmake -DCMAKE_BUILD_TYPE=debug -xo_toplevel_debug_config2() +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index aef60b2f..edc997ea 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -1,7 +1,12 @@ # 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) +set(SELF_SOURCE_FILES + reflect_utest_main.cpp + StructReflector.test.cpp + VectorTdx.test.cpp + StructTdx.test.cpp + FunctionTdx.test.cpp) xo_add_utest_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) diff --git a/utest/FunctionTdx.test.cpp b/utest/FunctionTdx.test.cpp new file mode 100644 index 00000000..278a2532 --- /dev/null +++ b/utest/FunctionTdx.test.cpp @@ -0,0 +1,40 @@ +/* @file FunctionTdx.test.cpp */ + +#include "xo/reflect/Reflect.hpp" +#include + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::TypeDescr; + using xo::reflect::Metatype; + + namespace ut { + TEST_CASE("function-reflect1", "[reflect]") { + using FunctionType = double (*)(double); + + FunctionType fn = ::sqrt; + + TaggedPtr tp = Reflect::make_tp(&fn); + //TypeDescr td = Reflect::require>(); + + REQUIRE(Reflect::is_reflected() == true); + + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &fn); + REQUIRE(tp.is_function()); + REQUIRE(tp.is_pointer() == false); + REQUIRE(tp.is_vector() == false); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_function); + REQUIRE(tp.recover_native() == &fn); + REQUIRE(tp.n_child() == 0); /*not a composite*/ + // REQUIRE(tp.child_td(0) == ... + REQUIRE(tp.td()->fn_retval() == Reflect::require()); + REQUIRE(tp.n_fn_arg() == 1); + REQUIRE(tp.td()->fn_arg(0) == Reflect::require()); + } /*TEST_CASE(function-reflect1)*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end FunctionTdx.test.cpp */ From 3cc8e73ab4e07f66034b4c6fd4f6be2b8d18d7f1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 15:00:57 -0400 Subject: [PATCH 1017/2524] xo-pyjit: initial commit --- .gitignore | 6 ++++ CMakeLists.txt | 28 +++++++++++++++ cmake/xo-bootstrap-macros.cmake | 35 +++++++++++++++++++ cmake/xo_pyjitConfig.cmake.in | 7 ++++ include/README.md | 1 + src/pyjit/CMakeLists.txt | 9 +++++ src/pyjit/pyjit.cpp | 60 +++++++++++++++++++++++++++++++++ src/pyjit/pyjit.hpp.in | 25 ++++++++++++++ 8 files changed, 171 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_pyjitConfig.cmake.in create mode 100644 include/README.md create mode 100644 src/pyjit/CMakeLists.txt create mode 100644 src/pyjit/pyjit.cpp create mode 100644 src/pyjit/pyjit.hpp.in diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..13c0afb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..bacd5c53 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,28 @@ +# xo-pyjit/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pyjit VERSION 0.1) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- + +add_subdirectory(src/pyjit) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/cmake/xo_pyjitConfig.cmake.in b/cmake/xo_pyjitConfig.cmake.in new file mode 100644 index 00000000..5d7de08a --- /dev/null +++ b/cmake/xo_pyjitConfig.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(xo_jit) +find_dependency(xo_pyexpression) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/README.md b/include/README.md new file mode 100644 index 00000000..9db0605f --- /dev/null +++ b/include/README.md @@ -0,0 +1 @@ +placeholder for future xo-pymatrix header files diff --git a/src/pyjit/CMakeLists.txt b/src/pyjit/CMakeLists.txt new file mode 100644 index 00000000..d6d84521 --- /dev/null +++ b/src/pyjit/CMakeLists.txt @@ -0,0 +1,9 @@ +# xo-pyjit/src/pyjit/CMakeLists.txt + +set(SELF_LIB xo_pyjit) +set(SELF_SRCS pyjit.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} xo_jit) +xo_pybind11_dependency(${SELF_LIB} xo_pyexpression) +xo_dependency(${SELF_LIB} refcnt) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp new file mode 100644 index 00000000..a8a532ec --- /dev/null +++ b/src/pyjit/pyjit.cpp @@ -0,0 +1,60 @@ +/* @file pyjit.cpp */ + +#include "pyjit.hpp" +#include "xo/pyexpression/pyexpression.hpp" +#include "xo/jit/Jit.hpp" +#include "xo/pyutil/pyutil.hpp" + +namespace xo { + namespace jit { + using xo::ast::Expression; + using xo::ref::rp; + using xo::ref::unowned_ptr; + namespace py = pybind11; + + PYBIND11_MODULE(XO_PYJIT_MODULE_NAME(), m) { + // e.g. for xo::ast::Expression + XO_PYEXPRESSION_IMPORT_MODULE(); // py::module_::import("pyexpression"); + + m.doc() = "pybind11 plugin for xo-jit"; + + py::class_>(m, "Jit") + .def_static("make", &Jit::make, + py::doc("create Jit instance. Not threadsafe," + " but does not share resources with any other Jit instance")) + + .def("codegen", + [](Jit & jit, const rp & expr) { + return jit.codegen(expr.borrow()); + }, + py::arg("x"), + py::doc("generate llvm (IR) code for Expression x"), + /* we're assuming llvm-generated code lives for as long as the Jit + * instance that created it. + * + * RC 14jun2024 - I think this is true modulo use of llvm resource trackers. + */ + py::return_value_policy::reference_internal) + ; + + py::class_>(m, "llvm_Value") + .def("print", + [](llvm::Value & x) { + std::string buf; + llvm::raw_string_ostream ss(buf); + x.print(ss); + return buf; + }) + .def("__repr__", + &Jit::display_string) + ; + + } + + + } /*namespace jit*/ +} /*namespace xo*/ + + +/* end pyjit.cpp */ diff --git a/src/pyjit/pyjit.hpp.in b/src/pyjit/pyjit.hpp.in new file mode 100644 index 00000000..8abdc8e3 --- /dev/null +++ b/src/pyjit/pyjit.hpp.in @@ -0,0 +1,25 @@ +/* @file pyjit.hpp + * + * automatically generated from src/xo_pyjit/pyjit.hpp.in + * see src/xo_pyjit/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(XO_PYJIT_MODULE_NAME(), m) { ... } + */ +#define XO_PYJIT_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(XO_PYJIT_MODULE_NAME_STR) + */ +#define XO_PYJIT_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * XO_PYJIT_IMPORT_MODULE() + * replaces + * py::module_::import("pyjit") + */ +#define XO_PYJIT_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pyjit.hpp */ From 5b8b08145b250749d5d50cf004747edf14687e6b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 15:05:47 -0400 Subject: [PATCH 1018/2524] + README.md --- README.md | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..9e55c217 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# python bindings for llvm JIT for EGAD (xo-pyjit) + +## Getting Started + +### Build + install dependencies + +- [github/Rconybea/xo-jit](https://github.com/Rconybea/xo-jit) +- [github/Rconybea/xo-pyexpression](https://github.com/Rconybea/xo-pyexpression) + +### build + isntall + +``` +$ cd xo-pyjit +$ PREFIX=/usr/local # or preferred install location +$ cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -S . -B .build +$ cmake --build .build -j +$ cmake --install .build +``` +(also see .github/workflows/main.yml) + +## Examples + +Assumes `xo-pyjit` installed to `~/local2/lib`, +i.e. built with `PREFIX=~/local2`. +``` +PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> from xo_pyexpression import * +>>> from xo_pyjit import * +>>> x=make_constant(3.14159) +>>> jit=Jit.make() +>>> code=jit.codegen(x) +>>> x.print +double 3.141600e+00 +``` + +## Development + +### use from build tree + +Limited utility: requires that supporting libraries (e.g. `xo_pyexpression`) appear in PYTHONPATH +``` +$ cd xo-pyjit/.build/src/pyjit +$ python +>>> import xo_pyjit +``` + +### build for unit test coverage +``` +$ cd xo-pyexpression +$ cmake -DCMAKE_BUILD_TYPE=coverage -DENABLE_TESTING=on -S . -B .build-ccov +$ cmake --build .build-ccov -j +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pyexpression +$ ln -s .build/compile_commands.json # supply compile commands to LSP +``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-pyjit/.build +$ cmake -LAH +``` From d315d4212b781e6b0ab4e2aed6a0363b35ab05dc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 15:06:15 -0400 Subject: [PATCH 1019/2524] .gitignore: + ignore .projectile --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 13c0afb7..e6766880 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# emacs configuration for workspace +.projectile # clangd working space (see emacs+lsp) .cache # typical cmake build directory (source-tree-nephew) From 451490145924d229233b2fe2c16edc00de773f55 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 15:07:08 -0400 Subject: [PATCH 1020/2524] xo-jit: + display + display_string --- include/xo/jit/Jit.hpp | 11 ++++++++++- src/jit/Jit.cpp | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 4c1f1534..044343b8 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -59,6 +59,9 @@ namespace xo { llvm::Value * codegen(ref::brw expr); + virtual void display(std::ostream & os) const; + virtual std::string display_string() const; + private: Jit(); @@ -83,7 +86,13 @@ namespace xo { * corresponding llvm interactor **/ std::map nested_env_; - }; + }; /*Jit*/ + + inline std::ostream & + operator<<(std::ostream & os, const Jit & x) { + x.display(os); + return os; + } } /*namespace jit*/ } /*namespace xo*/ diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index d9fc0f61..32bf55d4 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -244,6 +244,15 @@ namespace xo { return nullptr; } /*codegen*/ + void + Jit::display(std::ostream & os) const { + os << ""; + } + + std::string + Jit::display_string() const { + return tostr(*this); + } } /*namespace jit*/ } /*namespace xo*/ From 4b8fab54c346c5590cc609d99e40d4b7cba29cda Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 15:13:53 -0400 Subject: [PATCH 1021/2524] xo-cmake: + xo-build assistant --- CMakeLists.txt | 7 ++ README.md | 2 + bin/xo-build.in | 214 +++++++++++++++++++++++++++++++++++++++++ bin/xo-cmake-config.in | 2 +- 4 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 bin/xo-build.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e28d3fe..4b7c1036 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,12 @@ configure_file( @ONLY ) +configure_file( + ${PROJECT_SOURCE_DIR}/bin/xo-build.in + ${PROJECT_BINARY_DIR}/xo-build + @ONLY + ) + install( FILES "cmake/xo_macros/xo-project-macros.cmake" @@ -36,6 +42,7 @@ install( FILES "${PROJECT_BINARY_DIR}/xo-cmake-lcov-harness" "${PROJECT_BINARY_DIR}/xo-cmake-config" + "${PROJECT_BINARY_DIR}/xo-build" PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE DESTINATION ${CMAKE_INSTALL_BINDIR} ) diff --git a/README.md b/README.md index db3b4dd8..8ccf76bb 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ $ cp $PREFIX/share/xo-macros/xo-bootstrap-macros.cmake cmake/ then in `foo/CMakeLists.txt`: ``` include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() ``` Now as long as `$PREFIX/bin` is in `PATH`: diff --git a/bin/xo-build.in b/bin/xo-build.in new file mode 100644 index 00000000..dbbfc187 --- /dev/null +++ b/bin/xo-build.in @@ -0,0 +1,214 @@ +#!/usr/bin/env bash + +usage() { + echo "$0 [-u|--usage|-h|--help] [--xoname=NAME] [--repo]" +} + +help() { + usage + + cat < 0 ]]; do + case "$1" in + -u | --usage) + cmd='usage' + ;; + -h | --help) + cmd='help' + ;; + -n) + noop_flag=1 + ;; + --list) + cmd='list' + ;; + --xoname=*) + xoname="${1#*=}" + ;; + --repo) + repo_flag=1 + ;; + --clone) + clone_flag=1 + ;; + --configure|--config) + configure_flag=1 + ;; + --build) + build_flag=1 + ;; + --install) + install_flag=1 + ;; + xo-*) + xoname="$1" + ;; + *) + usage + exit 1 + ;; + esac + + shift +done + +subsystem_list() { + cat < Date: Fri, 14 Jun 2024 15:21:49 -0400 Subject: [PATCH 1022/2524] xo-refcnt: exclude logging unless XO_INTRUSIVE_PTR_ENABLE_LOGGING --- include/xo/refcnt/Refcounted.hpp | 32 ++++++++++++++++++++++++++++++-- src/Refcounted.cpp | 2 ++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/include/xo/refcnt/Refcounted.hpp b/include/xo/refcnt/Refcounted.hpp index 3db621a1..b54d5b5e 100644 --- a/include/xo/refcnt/Refcounted.hpp +++ b/include/xo/refcnt/Refcounted.hpp @@ -28,7 +28,9 @@ namespace xo { public: intrusive_ptr() : ptr_(nullptr) {} intrusive_ptr(T * x) : ptr_(x) { +#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_ctor(sc_self_type, this, x); +#endif intrusive_ptr_add_ref(ptr_); } /*ctor*/ @@ -38,20 +40,26 @@ namespace xo { * instrusive_ptr. */ intrusive_ptr(intrusive_ptr const & x) : ptr_(x.get()) { +#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_cctor(sc_self_type, this, x.get()); +#endif intrusive_ptr_add_ref(ptr_); } /*cctor*/ /* create from instrusive pointer to some related type S */ template intrusive_ptr(intrusive_ptr const & x) : ptr_(x.get()) { +#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_cctor(sc_self_type, this, x.get()); +#endif intrusive_ptr_add_ref(ptr_); } /*cctor*/ /* move ctor -- in this case don't need to update refcount */ intrusive_ptr(intrusive_ptr && x) : ptr_{std::move(x.ptr_)} { +#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_mctor(sc_self_type, this, ptr_); +#endif /* since we're moving from x, need to make sure x dtor * doesn't decrement refcount */ @@ -66,7 +74,9 @@ namespace xo { template intrusive_ptr(intrusive_ptr const & /*x*/, element_type * y) : ptr_{y} { if (std::is_same()) { +#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_actor(sc_self_type, this, y); +#endif intrusive_ptr_add_ref(ptr_); ; /* trivial aliasing, proceed */ } else { @@ -80,7 +90,9 @@ namespace xo { ~intrusive_ptr() { T * x = this->ptr_; +#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_dtor(sc_self_type, this, x); +#endif this->ptr_ = nullptr; @@ -103,7 +115,9 @@ namespace xo { intrusive_ptr & operator=(intrusive_ptr const & rhs) { T * x = rhs.get(); +#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_assign(sc_self_type, this, x); +#endif T * old = this->ptr_; this->ptr_ = x; @@ -115,7 +129,9 @@ namespace xo { } /*operator=*/ intrusive_ptr & operator=(intrusive_ptr && rhs) { +#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_massign(sc_self_type, this, rhs.get()); +#endif std::swap(this->ptr_, rhs.ptr_); @@ -134,7 +150,8 @@ namespace xo { }; /*intrusive_ptr*/ template - inline bool operator==(intrusive_ptr const & x, intrusive_ptr const & y) { return intrusive_ptr::compare(x, y) == 0; } + inline bool operator==(intrusive_ptr const & x, + intrusive_ptr const & y) { return intrusive_ptr::compare(x, y) == 0; } template using rp = intrusive_ptr; @@ -171,6 +188,7 @@ namespace xo { return 0; } /*intrusive_ptr_refcount*/ +#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING extern void intrusive_ptr_set_debug(bool x); extern void intrusive_ptr_log_ctor(std::string_view const & self_type, void * this_ptr, @@ -193,7 +211,8 @@ namespace xo { Refcount * x); extern void intrusive_ptr_log_massign(std::string_view const & self_type, void *this_ptr, - Refcount * x); + Refcount * x); +#endif extern void intrusive_ptr_add_ref(Refcount * x); extern void intrusive_ptr_release(Refcount * x); @@ -208,6 +227,15 @@ namespace xo { return os; } /*operator<<*/ + /** Wrap a (presumably non-reference-counted) class T so that it has a refcount. + **/ + template + class RefcountWrapper : public Refcount, public T { + public: + template + RefcountWrapper(Args... args) : Refcount(), T(std::forward(args...)) {} + }; + /* borrow a reference-counted pointer to pass down the stack * 1. borrowed pointer intended to replace: * a. code like diff --git a/src/Refcounted.cpp b/src/Refcounted.cpp index eade7a50..38accc85 100644 --- a/src/Refcounted.cpp +++ b/src/Refcounted.cpp @@ -4,6 +4,7 @@ namespace xo { namespace ref { +#ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING namespace { /* verbose logging for intrusive_ptr */ static bool s_logging_enabled = false; @@ -89,6 +90,7 @@ namespace xo { if (s_logging_enabled) intrusive_ptr_log_aux(self_type, "::m=", this_ptr, x); } /*intrusive_ptr_log_massign*/ +#endif void intrusive_ptr_add_ref(Refcount * x) From ae9acf59b333e74f63b71ccb194a97dd1c0c9e80 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 15:22:15 -0400 Subject: [PATCH 1023/2524] xo-refcnt: README: mention xo-build assistant --- README.md | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 0b53dc81..19a14a1d 100644 --- a/README.md +++ b/README.md @@ -10,28 +10,41 @@ Refcnt is a small shared library supplying intrusive reference counting. ## Getting Started -### build + install `indentlog` dependency +### build + install `xo-cmake` dependency (cmake macros) -see [github/Rconybea/indentlog](https://github.com/Rconybea/indentlog) +see [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) + +Installs a few cmake ingredients, along with a build assistant for XO projects such as this one. + +### build + install XO deps +``` +$ xo-build --clone --configure --build --install xo-indentlog +``` ### copy `refcnt` repository locally ``` -$ git clone git@github.com:rconybea/refcnt.git -$ ls -d xo-refcnt -xo-refcnt +$ xo-build --clone xo-refcnt +``` + +or equivalently +``` +$ git clone git@github.com:rconybea/refcnt.git xo-refcnt ``` ### build + install ``` -$ cd xo-refcnt -$ mkdir build -$ cd build -$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. -$ make -$ make install +$ xo-build --configure --build --install xo-refcnt ``` -`CMAKE_PREFIX_PATH` should point to prefix where `indentlog` is installed +or equivalently: +``` +$ mkdir xo-refcnt/.build +$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-refcnt -B xo-refcnt/.build +$ cmake --build xo-refcnt/.build +$ cmake --install xo-refcnt/.build +``` + +`CMAKE_PREFIX_PATH` should point to the prefix where `xo-indentlog` is installed alternatively, if you're a nix user: ``` @@ -44,10 +57,8 @@ $ nix-build -A xo-refcnt ### build for unit test coverage ``` -$ cd xo-refcnt -$ mkdir ccov -$ cd ccov -$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_PREFIX_PATH=${PREFIX} -S xo-refcnt -B xo-refcnt/.build-ccov +$ cmake --build xo-refcnt/.build-ccov ``` ### LSP support From 6451b126171cdeb633f66f0ce1fc9ea0a033c513 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 15:22:30 -0400 Subject: [PATCH 1024/2524] xo-refcnt: build: streamline --- CMakeLists.txt | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 336947d3..4ef7c6c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,15 +7,7 @@ project(refcnt VERSION 0.1) include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) -xo_cxx_toplevel_options2() - -# ---------------------------------------------------------------- -# cmake -DCMAKE_BUILD_TYPE=debug -xo_toplevel_debug_config2() - -# ---------------------------------------------------------------- -# cmake -DCMAKE_BUILD_TYPE=coverage -xo_toplevel_coverage_config2() +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings From 8c3c3a7ac4230b648aef39debb576c5788daae30 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 15:23:01 -0400 Subject: [PATCH 1025/2524] .gitignore: + .projectile --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index cc75cfd3..39d62bcb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# emacs workspace config +.projectile # symlink to ${mybuilddirectory}/compile_commands.json for LSP compile_commands.json # LSP keeps state here From f5b08526e67b72854cca4ebb4f4675e504558a5f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 15:35:08 -0400 Subject: [PATCH 1026/2524] xo-expression: + README.md --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..7279b3fa --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# xo-expression library + +A library for representing abstract syntax trees for EGAD (a small expression-based language). + +## Getting Started + +### build + install `xo-cmake` dependency + +- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) + +Installs a few cmake ingredients, along with a build assistant `xo-build` for XO projects such as this one. + +### build + install other XO dependencies +``` +$ xo-build --clone --configure --build --install xo-indentlog +$ xo-build --clone --configure --build --install xo-refnct +$ xo-build --clone --configure --build --install xo-subsys +$ xo-build --clone --configure --build --install xo-reflect +``` + +Note: can use `xo-build -n` to dry-run here + +### copy `xo-expression` repository locally +``` +$ xo-build --clone xo-expression +``` + +or eqivalently +``` +$ git clone git@github.com:Rconybea/xo-expression.git +``` + +### build + install xo-expression +``` +$ xo-build --configure --build --install xo-expression +``` + +or equivalently: +``` +$ PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-expression -B xo-reflect/.build +$ cmake --build xo-expression/.build +$ cmake --install xo-expression/.build +``` + +### build for unit test coverage +``` +$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_INSTALL_PREFIX=$PREFIX xo-expression/.build-ccov +$ cmake --build xo-expression/.build-ccov +``` + +### LSP support +``` +$ cd xo-expression +$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` From 2b32aa83c7a05fd665cdafc69ab9aee99e73545d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 15:36:47 -0400 Subject: [PATCH 1027/2524] xo-expression: README: fix copypasta --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7279b3fa..205ba405 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ $ xo-build --configure --build --install xo-expression or equivalently: ``` $ PREFIX=/usr/local # or wherever you prefer -$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-expression -B xo-reflect/.build +$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-expression -B xo-jit/.build $ cmake --build xo-expression/.build $ cmake --install xo-expression/.build ``` From f8b73140557fbadb6fae00134119761bd47e6ed2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 15:42:24 -0400 Subject: [PATCH 1028/2524] xo-pyjit: README: expand + use xo-build for XO deps --- README.md | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9e55c217..f78c49fd 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,45 @@ ## Getting Started -### Build + install dependencies +### Build + install `xo-cmake` dependency -- [github/Rconybea/xo-jit](https://github.com/Rconybea/xo-jit) -- [github/Rconybea/xo-pyexpression](https://github.com/Rconybea/xo-pyexpression) +- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) -### build + isntall +Installs a few cmake ingredients, along with a build assistant `xo-build` for XO projects such as this one. + +### build + install other necessary XO dependencies +``` +$ xo-build --clone --configure --build --install xo-indentlog +$ xo-build --clone --configure --build --install xo-refnct +$ xo-build --clone --configure --build --install xo-subsys +$ xo-build --clone --configure --build --install xo-reflect +$ xo-build --clone --configure --build --install xo-expression +$ xo-build --clone --configure --build --install xo-jit +$ xo-build --clone --configure --build --install xo-pyutil +$ xo-build --clone --configure --build --install xo-pyexpression +``` +### copy `xo-pyjit` repository locally +``` +$ xo-build --clone xo-pyjit +``` + +or equivalently +``` +$ git clone git@github.com:Rconybea/xo-pyjit.git +``` + +### build + install xo-pyjit +``` +$ xo-build --configure --build --install xo-pyjit +``` + +or equivalently: ``` -$ cd xo-pyjit $ PREFIX=/usr/local # or preferred install location -$ cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -S . -B .build -$ cmake --build .build -j -$ cmake --install .build +$ cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -S xo-pyjit -B xo-pyjit/.build +$ cmake --build xo-pyjit/.build -j +$ cmake --install xo-pyjit/.build ``` (also see .github/workflows/main.yml) From 82b725442ef87c68e752052b6bc5fe5cdef86af0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 15:46:15 -0400 Subject: [PATCH 1029/2524] + README --- README.md | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..9b61eef5 --- /dev/null +++ b/README.md @@ -0,0 +1,94 @@ +# python bindings for Egad abstract syntax tree library (xo-expression) + +## Getting Started + +### Build + install `xo-cmake` dependency + +- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) + +Installs a few cmake ingredients, along with a build assistant `xo-build` for XO projects such as this one. + +### Build + install other necessary XO dependencies + +``` +$ xo-build --clone --configure --build --install xo-indentlog +$ xo-build --clone --configure --build --install xo-refnct +$ xo-build --clone --configure --build --install xo-subsys +$ xo-build --clone --configure --build --install xo-reflect +$ xo-build --clone --configure --build --install xo-expression +$ xo-build --clone --configure --build --install xo-jit +$ xo-build --clone --configure --build --install xo-pyutil +``` + +### copy `xo-pyexpression` repository locally +``` +$ xo-build --clone xo-pyexpression` +``` + +or equivalently +``` +$ git clone git@github.com:Rconybea/xo-pyexpression.git +``` + +### build + install xo-pyexpression +``` +$ xo-build --configure --build --install xo-pyexpression +``` + +or equivalently: +``` +$ PREFIX=/usr/local # or preferred install location +$ cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -S xo-pyexpression -B xo-pyyexpression/.build +$ cmake --build xo-pyexpression/.build -j +$ cmake --install xo-pyexpression/.build +``` +(also see .github/workflows/main.yml) + +## Examples + +Assumes `xo-pyexpression` installed to `~/local2/lib`, +i.e. built with `PREFIX=~/local2`. +``` +PYTHONPATH=~/locasl2/lib:$PYTHONPATH python +>>> import xo_pyexpression +>>> dir(xo_pyexpression) +['Expression', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'exprtype'] +``` + +## Development + +### use from build tree +Requires that supporting libraries (e.g. `xo_pyreflect`) appear in PYTHONPATH +``` +$ cd xo-pyexpression/.build/src/pyexpression +$ python +>>> import xo_pyexpression +``` + +### build for unit test coverage +``` +$ cd xo-pyexpression +$ cmake -DCMAKE_BUILD_TYPE=coverage -DENABLE_TESTING=on -S . -B .build-ccov +$ cmake --build .build-ccov -j +``` + +### LSP (language server) support + +LSP looks for compile commands in the root of the source tree; +while Cmake creates them in the root of its build directory. + +``` +$ cd xo-pyexpression +$ ln -s .build/compile_commands.json # supply compile commands to LSP +``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-pyexpression/.build +$ cmake -LAH +``` From 1ead5333b0d45b0ff1040d6db8f8671613e5b83a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 15:46:40 -0400 Subject: [PATCH 1030/2524] xo-pyexpression: mvp working --- .gitignore | 8 +++ CMakeLists.txt | 28 ++++++++++ cmake/xo-bootstrap-macros.cmake | 35 +++++++++++++ cmake/xo_pyexpressionConfig.cmake.in | 7 +++ include/REAMDE.md | 1 + src/pyexpression/CMakeLists.txt | 9 ++++ src/pyexpression/pyexpression.cpp | 76 ++++++++++++++++++++++++++++ src/pyexpression/pyexpression.hpp.in | 25 +++++++++ 8 files changed, 189 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_pyexpressionConfig.cmake.in create mode 100644 include/REAMDE.md create mode 100644 src/pyexpression/CMakeLists.txt create mode 100644 src/pyexpression/pyexpression.cpp create mode 100644 src/pyexpression/pyexpression.hpp.in diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3d3a7826 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# emacs workspace config +.projectile +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..31f734f4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,28 @@ +# xo-pyexpression/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_pyexpression VERSION 0.1) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- + +add_subdirectory(src/pyexpression) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/cmake/xo_pyexpressionConfig.cmake.in b/cmake/xo_pyexpressionConfig.cmake.in new file mode 100644 index 00000000..dc1d5e3d --- /dev/null +++ b/cmake/xo_pyexpressionConfig.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(xo_expression) +find_dependency(xo_pyreflect) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/REAMDE.md b/include/REAMDE.md new file mode 100644 index 00000000..9db0605f --- /dev/null +++ b/include/REAMDE.md @@ -0,0 +1 @@ +placeholder for future xo-pymatrix header files diff --git a/src/pyexpression/CMakeLists.txt b/src/pyexpression/CMakeLists.txt new file mode 100644 index 00000000..6e81f021 --- /dev/null +++ b/src/pyexpression/CMakeLists.txt @@ -0,0 +1,9 @@ +# xo-pyexpression/src/pyexpression/CMakeLists.txt + +set(SELF_LIB xo_pyexpression) +set(SELF_SRCS pyexpression.cpp) + +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} xo_expression) +xo_pybind11_dependency(${SELF_LIB} xo_pyreflect) +xo_dependency(${SELF_LIB} refcnt) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp new file mode 100644 index 00000000..a87f6b06 --- /dev/null +++ b/src/pyexpression/pyexpression.cpp @@ -0,0 +1,76 @@ +/* @file pyexpression.cpp */ + +#include "pyexpression.hpp" +#include "xo/pyreflect/pyreflect.hpp" +#include "xo/expression/Expression.hpp" +#include "xo/expression/ConstantInterface.hpp" +#include "xo/expression/Constant.hpp" +#include "xo/pyutil/pyutil.hpp" + +namespace xo { + namespace ast { + using xo::ast::exprtype; + using xo::ast::Expression; + using xo::ast::ConstantInterface; + using xo::ast::Constant; + using xo::reflect::TaggedPtr; + using xo::ref::rp; + namespace py = pybind11; + + PYBIND11_MODULE(XO_PYEXPRESSION_MODULE_NAME(), m) { + // e.g. for xo::reflect::TypeDescr + PYREFLECT_IMPORT_MODULE(); // py::module_::import("pyreflect"); + + m.doc() = "pybind11 plugin for xo-expression"; + + py::enum_(m, "exprtype") + .value("invalid", exprtype::invalid) + .value("constant", exprtype::constant) + .value("primitive", exprtype::primitive) + .value("apply", exprtype::apply) + .value("lambda", exprtype::lambda) + .value("variable", exprtype::variable) + ; + + py::class_>(m, "Expression") + .def("extype", &Expression::extype) + .def("__repr__", &Expression::display_string); + ; + + py::class_>(m, "ConstantInterface") + .def("value_td", &ConstantInterface::value_td, + py::doc("type description for literal value represented by this Constant")) + .def("value", + [](const ConstantInterface & expr) { + TaggedPtr tp = expr.value_tp(); + + auto * p = tp.recover_native(); + + /* TODO: promote to pyobject, so we can do polymorphism */ + if (p) + return *p; + else + return std::numeric_limits::quiet_NaN(); + }, + py::doc("recover constant expression's wrapped value [wip - only works for double]")) + ; + + py::class_, ConstantInterface, rp>>(m, "Constant_double") + ; + + m.def("make_constant", + [](double x) { + return make_constant(x); + }, + py::arg("x"), + py::doc("make_constant(x) creates constant expression holding x [wip - only works for double")) + ; + + } /*pyexpresion*/ + } /*namespace ast*/ +} /*namespace xo*/ + +/* end pyexpression.cpp */ diff --git a/src/pyexpression/pyexpression.hpp.in b/src/pyexpression/pyexpression.hpp.in new file mode 100644 index 00000000..7f4f4660 --- /dev/null +++ b/src/pyexpression/pyexpression.hpp.in @@ -0,0 +1,25 @@ +/* @file pyexpression.hpp + * + * automatically generated from src/xo_xo_pyexpression/xo_pyexpression.hpp.in + * see src/xo_xo_pyexpression/CMakeLists.txt + */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(XO_PYEXPRESSION_MODULE_NAME(), m) { ... } + */ +#define XO_PYEXPRESSION_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(XO_PYEXPRESSION_MODULE_NAME_STR) + */ +#define XO_PYEXPRESSION_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * XO_PYEXPRESSION_IMPORT_MODULE() + * replaces + * py::module_::import("xo_pyexpression") + */ +#define XO_PYEXPRESSION_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pyexpression.hpp */ From 27a0dd591f7ca6454bbfd782bc9cd8ae863f44a4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 16:11:37 -0400 Subject: [PATCH 1031/2524] xo-pyexpression: + Primitive + PrimitiveInterface + examples --- src/pyexpression/pyexpression.cpp | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index a87f6b06..c42fbcc7 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -5,7 +5,10 @@ #include "xo/expression/Expression.hpp" #include "xo/expression/ConstantInterface.hpp" #include "xo/expression/Constant.hpp" +#include "xo/expression/PrimitiveInterface.hpp" +#include "xo/expression/Primitive.hpp" #include "xo/pyutil/pyutil.hpp" +#include namespace xo { namespace ast { @@ -13,6 +16,9 @@ namespace xo { using xo::ast::Expression; using xo::ast::ConstantInterface; using xo::ast::Constant; + using xo::ast::PrimitiveInterface; + using xo::ast::Primitive; + using xo::ast::make_primitive; using xo::reflect::TaggedPtr; using xo::ref::rp; namespace py = pybind11; @@ -38,6 +44,8 @@ namespace xo { .def("__repr__", &Expression::display_string); ; + // ----- Constants ----- + py::class_>(m, "ConstantInterface") @@ -69,6 +77,31 @@ namespace xo { py::doc("make_constant(x) creates constant expression holding x [wip - only works for double")) ; + // ----- Primitives ----- + + py::class_>(m, "PrimitiveInterface") + .def("name", &PrimitiveInterface::name, + py::doc("name of this primitive function; use this name to invoke the function")) + .def("n_arg", &PrimitiveInterface::n_arg, + py::doc("number of arguments to this function (not counting return value)")) + ; + + using Fn_dbl_dbl_type = double (*)(double); + + m.def("make_sqrt_pm", []() { return make_primitive("sqrt", sqrt); }, + py::doc("create primitive representing the ::sqrt() function")); + m.def("make_sin_pm", []() { return make_primitive("sin", ::sin); }, + py::doc("create primitive representing the ::sin() function")); + m.def("make_cos_pm", []() { return make_primitive("cos", ::cos); }, + py::doc("create primitive representing the ::cos() function")); + + py::class_, + PrimitiveInterface, + rp>>(m, "Primitive_double_double") + ; + } /*pyexpresion*/ } /*namespace ast*/ } /*namespace xo*/ From 94d03915854ee6ca1493f5c22f8300580cdd6bfd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 16:12:22 -0400 Subject: [PATCH 1032/2524] xo-pyjit: minor -- drop printing to procrastinate on compiler nit --- README.md | 2 ++ src/pyjit/pyjit.cpp | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f78c49fd..1f46c9a5 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ $ xo-build --clone --configure --build --install xo-jit $ xo-build --clone --configure --build --install xo-pyutil $ xo-build --clone --configure --build --install xo-pyexpression ``` +note: can use `xo-build -n` to dry-run here + ### copy `xo-pyjit` repository locally ``` $ xo-build --clone xo-pyjit diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index a8a532ec..db202824 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -46,8 +46,8 @@ namespace xo { x.print(ss); return buf; }) - .def("__repr__", - &Jit::display_string) +// .def("__repr__", +// &Jit::display_string) ; } From 3d354f76575d5625deeb22e1bdd6652facf30f86 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 16:18:55 -0400 Subject: [PATCH 1033/2524] xo-pyexpression: + Apply expressions --- src/pyexpression/pyexpression.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index c42fbcc7..6fecc65b 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -3,10 +3,11 @@ #include "pyexpression.hpp" #include "xo/pyreflect/pyreflect.hpp" #include "xo/expression/Expression.hpp" -#include "xo/expression/ConstantInterface.hpp" -#include "xo/expression/Constant.hpp" +#include "xo/expression/Apply.hpp" #include "xo/expression/PrimitiveInterface.hpp" #include "xo/expression/Primitive.hpp" +#include "xo/expression/ConstantInterface.hpp" +#include "xo/expression/Constant.hpp" #include "xo/pyutil/pyutil.hpp" #include @@ -14,11 +15,12 @@ namespace xo { namespace ast { using xo::ast::exprtype; using xo::ast::Expression; - using xo::ast::ConstantInterface; - using xo::ast::Constant; + using xo::ast::make_apply; using xo::ast::PrimitiveInterface; using xo::ast::Primitive; using xo::ast::make_primitive; + using xo::ast::ConstantInterface; + using xo::ast::Constant; using xo::reflect::TaggedPtr; using xo::ref::rp; namespace py = pybind11; @@ -80,7 +82,7 @@ namespace xo { // ----- Primitives ----- py::class_>(m, "PrimitiveInterface") .def("name", &PrimitiveInterface::name, py::doc("name of this primitive function; use this name to invoke the function")) @@ -102,6 +104,17 @@ namespace xo { rp>>(m, "Primitive_double_double") ; + // ----- Apply ----- + + py::class_>(m, "Apply") + .def_property_readonly("fn", &Apply::fn, py::doc("function to be invoked")) + .def_property_readonly("argv", &Apply::argv, py::doc("expressions (in position order) for function arguments")) + ; + + m.def("make_apply", &make_apply); + } /*pyexpresion*/ } /*namespace ast*/ } /*namespace xo*/ From a9deebaa90e77757dfc7d335c282fbef5b834713 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 16:23:01 -0400 Subject: [PATCH 1034/2524] xo-pyexpression: bugfix: missing #includes for pybind11 opt feats --- src/pyexpression/pyexpression.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index 6fecc65b..2a9c38d3 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -9,6 +9,8 @@ #include "xo/expression/ConstantInterface.hpp" #include "xo/expression/Constant.hpp" #include "xo/pyutil/pyutil.hpp" +#include +#include #include namespace xo { From 97c0613fee0174f7856b1be31c1af5dddd3a976b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 16:23:21 -0400 Subject: [PATCH 1035/2524] xo-pyexpression: Expression.extype -> readonly property --- src/pyexpression/pyexpression.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index 2a9c38d3..01447f4f 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -44,7 +44,7 @@ namespace xo { py::class_>(m, "Expression") - .def("extype", &Expression::extype) + .def_property_readonly("extype", &Expression::extype) .def("__repr__", &Expression::display_string); ; From 7fcb0026f1f46635965ac7f4f2ec56b0d97e27e5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 16:26:23 -0400 Subject: [PATCH 1036/2524] pyexpression: + Variable + make_var --- src/pyexpression/pyexpression.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index 01447f4f..2c8ca496 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -8,6 +8,7 @@ #include "xo/expression/Primitive.hpp" #include "xo/expression/ConstantInterface.hpp" #include "xo/expression/Constant.hpp" +#include "xo/expression/Variable.hpp" #include "xo/pyutil/pyutil.hpp" #include #include @@ -23,6 +24,8 @@ namespace xo { using xo::ast::make_primitive; using xo::ast::ConstantInterface; using xo::ast::Constant; + using xo::ast::Variable; + using xo::ast::make_var; using xo::reflect::TaggedPtr; using xo::ref::rp; namespace py = pybind11; @@ -117,6 +120,14 @@ namespace xo { m.def("make_apply", &make_apply); + // ----- Variables ----- + + py::class_>(m, "Variable") + .def_property_readonly("name", &Variable::name, py::doc("variable name")) + ; + + m.def("make_var", &make_var); + } /*pyexpresion*/ } /*namespace ast*/ } /*namespace xo*/ From 42a4009d027fa72bfdb8e0f6f2f3e7f742128697 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 16:40:24 -0400 Subject: [PATCH 1037/2524] pyexpression: + Lambda + make_lambda --- src/pyexpression/pyexpression.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index 2c8ca496..ff808c0c 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -9,6 +9,7 @@ #include "xo/expression/ConstantInterface.hpp" #include "xo/expression/Constant.hpp" #include "xo/expression/Variable.hpp" +#include "xo/expression/Lambda.hpp" #include "xo/pyutil/pyutil.hpp" #include #include @@ -26,6 +27,8 @@ namespace xo { using xo::ast::Constant; using xo::ast::Variable; using xo::ast::make_var; + using xo::ast::Lambda; + using xo::ast::make_lambda; using xo::reflect::TaggedPtr; using xo::ref::rp; namespace py = pybind11; @@ -111,9 +114,7 @@ namespace xo { // ----- Apply ----- - py::class_>(m, "Apply") + py::class_>(m, "Apply") .def_property_readonly("fn", &Apply::fn, py::doc("function to be invoked")) .def_property_readonly("argv", &Apply::argv, py::doc("expressions (in position order) for function arguments")) ; @@ -128,6 +129,15 @@ namespace xo { m.def("make_var", &make_var); + // ----- Lambdas ----- + + py::class_>(m, "Lambda") + .def_property_readonly("name", &Lambda::name, py::doc("lambda name (maybe automatically generated?)")) + .def_property_readonly("argv", &Lambda::argv, py::doc("lambda formal parameters")) + .def_property_readonly("body", &Lambda::body, py::doc("lambda body expression")) + ; + + m.def("make_lambda", &make_lambda); } /*pyexpresion*/ } /*namespace ast*/ } /*namespace xo*/ From 49eaf6bc452e3a01c67aee461edcd637867fceb6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 16:58:35 -0400 Subject: [PATCH 1038/2524] xo-jit: + README --- README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..e2c4cbb3 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# xo-jit library + +A library for representing abstract syntax trees for EGAD (a small jit-based language). + +## Getting Started + +### build + install `xo-cmake` dependency + +- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) + +Installs a few cmake ingredients, along with a build assistant `xo-build` for XO projects such as this one. + +### build + install other necessary XO dependencies +``` +$ xo-build --clone --configure --build --install xo-indentlog +$ xo-build --clone --configure --build --install xo-refnct +$ xo-build --clone --configure --build --install xo-subsys +$ xo-build --clone --configure --build --install xo-reflect +$ xo-build --clone --configure --build --install xo-expression +``` + +Note: can use `xo-build -n` to dry-run here + +### copy `xo-jit` repository locally +``` +$ xo-build --clone xo-jit +``` + +or equivalently +``` +$ git clone git@github.com:Rconybea/xo-jit.git +``` + +### build + install xo-jit +``` +$ xo-build --configure --build --install xo-jit +``` + +or equivalently: +``` +$ PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-jit -B xo-jit/.build +$ cmake --build xo-jit/.build +$ cmake --install xo-jit/.build +``` + +### build for unit test coverage +``` +$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_INSTALL_PREFIX=$PREFIX xo-jit/.build-ccov +$ cmake --build xo-jit/.build-ccov +``` + +### LSP support +``` +$ cd xo-jit +$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` From 0c7b1e03d7d326bb6369c2dd30cc0356f642e462 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 16:58:50 -0400 Subject: [PATCH 1039/2524] xo-jit: handle multiple-argument primitives --- src/jit/Jit.cpp | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 32bf55d4..872b949e 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -61,10 +61,46 @@ namespace xo { // PLACEHOLDER // just make prototype for function :: double -> double - std::vector double_v(1, llvm::Type::getDoubleTy(*llvm_cx_)); + TypeDescr fn_td = expr->value_td(); + int n_fn_arg = fn_td->n_fn_arg(); - auto * llvm_fn_type = llvm::FunctionType::get(llvm::Type::getDoubleTy(*llvm_cx_), - double_v, + std::vector llvm_argtype_v(n_fn_arg); + + /** check function args are all doubles **/ + for (int i = 0; i < n_fn_arg; ++i) { + TypeDescr arg_td = fn_td->fn_arg(i); + + if (arg_td->is_native()) { + llvm_argtype_v.push_back(llvm::Type::getDoubleTy(*llvm_cx_)); + + // TODO: extend with other native types here... + } else { + cerr << "Jit::codegen_primitive: error: primitive f with arg i of type T where double expected" + << xtag("f", expr->name()) + << xtag("i", i) + << xtag("T", arg_td->short_name()) + << endl; + return nullptr; + } + } + + //std::vector double_v(n_fn_arg, llvm::Type::getDoubleTy(*llvm_cx_)); + + TypeDescr retval_td = fn_td->fn_retval(); + llvm::Type * llvm_retval = nullptr; + + if (retval_td->is_native()) { + llvm_retval = llvm::Type::getDoubleTy(*llvm_cx_); + } else { + cerr << "Jit::codegen_primitive: error: primitive f returning T where double expected" + << xtag("f", expr->name()) + << xtag("T", retval_td->short_name()) + << endl; + return nullptr; + } + + auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, + llvm_argtype_v, false /*!varargs*/); fn = llvm::Function::Create(llvm_fn_type, From 6c856be0fc20d643dd643ed73c82a26f107cb05a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 13:15:22 -0400 Subject: [PATCH 1040/2524] xo-reflect: README: howto using xo-build --- .gitignore | 2 ++ README.md | 51 ++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 13c0afb7..3d3a7826 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# emacs workspace config +.projectile # clangd working space (see emacs+lsp) .cache # typical cmake build directory (source-tree-nephew) diff --git a/README.md b/README.md index ecbafe0c..528d10e0 100644 --- a/README.md +++ b/README.md @@ -2,28 +2,49 @@ ## Getting Started -### build + install dependencies +### build + install `xo-cmake` dependency -- [github/Rconybea/refcnt](https://github.com/Rconybea/refcnt) -- [github/Rconybea/subsys](https://github.com/Rconybea/subsys) +- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) -### build + install +Installs a few cmake ingredients, along with a build assistant `xo-build` for XO projects such as this one. + +### build + install other XO dependencies ``` -$ cd reflect -$ mkdir build -$ cd build -$ INSTALL_PREFIX=/usr/local # or wherever you prefer -$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. -$ make -$ make install +$ xo-build --clone --configure --build --install xo-indentlog +$ xo-build --clone --configure --build --install xo-refnct +$ xo-build --clone --configure --build --install xo-subsys +``` + +Note: can use `-n` to dry-run here + +### copy `xo-reflect` repository locally +``` +$ xo-build --clone xo-reflect +``` + +or equivalently +``` +$ git clone git@github.com:Rconybea/xo-reflect.git +``` + +### build + install xo-reflect +``` +$ xo-build --configure --build --install xo-reflect +``` + +or equivalently: + +``` +$ PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-reflect -B xo-reflect/.build +$ cmake --build xo-reflect/.build +$ cmake --install xo-reflect/.build ``` ### build for unit test coverage ``` -$ cd xo-reflect -$ mkdir build-ccov -$ cd build-ccov -$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_INSTALL_PREFIX=$PREFIX xo-reflect/.build-ccov +$ cmake --build xo-reflect/.build-ccov ``` ### LSP support From 515cedf16043ce974455c1a07201963cf7d25532 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 13:15:57 -0400 Subject: [PATCH 1041/2524] xo-reflect: + EstablishFunctionTdx + bugfix deducing fptr inputs --- include/xo/reflect/Reflect.hpp | 110 ++++++++++++++++++-- include/xo/reflect/TypeDescr.hpp | 1 + include/xo/reflect/TypeDescrExtra.hpp | 3 +- include/xo/reflect/function/FunctionTdx.hpp | 18 +++- src/reflect/function/FunctionTdx.cpp | 19 +++- 5 files changed, 133 insertions(+), 18 deletions(-) diff --git a/include/xo/reflect/Reflect.hpp b/include/xo/reflect/Reflect.hpp index bba62ae1..20e862c9 100644 --- a/include/xo/reflect/Reflect.hpp +++ b/include/xo/reflect/Reflect.hpp @@ -72,16 +72,38 @@ namespace xo { // ----- Retval (*)(A1 .. An) ----- template - class EstablishTdx { + class EstablishTdx { public: /* definition provided after decl for Reflect {} below */ static std::unique_ptr make(); }; - // ----- Retval (*)() ----- + // ----- Retval (*)(A1 .. An) noexcept ----- - template - class EstablishTdx { + template + class EstablishTdx { + public: + /* definition provided after decl for Reflect {} below */ + static std::unique_ptr make(); + }; + + // ----- EstasblishFunctionTdx ------------------------------------- + + template + class EstablishFunctionTdx; + + // ----- Retval (*)(A1 .. An) ----- + + template + class EstablishFunctionTdx { + public: + /* definition provided after decl for Reflect {} below */ + static std::unique_ptr make(); + }; + + template + class EstablishFunctionTdx { + public: /* definition provided after decl for Reflect {} below */ static std::unique_ptr make(); }; @@ -170,6 +192,41 @@ namespace xo { return retval_td; } /*require*/ + /* can optionally use this when reflecting a function pointer. + * Should get the same result as reflect(), + * but will not fallback to AtomicTdx if T is not recognized as a function pointer + */ + template + static TypeDescrW require_function() { + //static_assert(std::is_function_v); + + TypeDescrW retval_td = EstablishTypeDescr::establish(); + + /* mark TypeDescr for T as complete (even though it isn't quite yet), + * so that when we encounter recursive types, reflection terminates. + * For example consider type resulting from code like + * + * typename T; + * using T = std::vector; + * + */ + if (retval_td->mark_complete()) { + /* control here on 2nd+later calls to require(). + * in principle can immediately short-circuit. + */ + } else { + /* control comes here the first time require() runs */ + + auto final_tdx = EstablishFunctionTdx::make(); + + retval_td->assign_tdextra(std::move(final_tdx)); + + /* also need to require for each child */ + } + + return retval_td; + } + /* Use: * T * xyz = ...; * TaggedPtr xyz_tp = Reflect::make_tp(xyz); @@ -256,7 +313,7 @@ namespace xo { return StructTdx::pair(); } /*make*/ - // ----- Retval (*) (A1 .. An) ----- + // ----- Retval(A1 .. An) ----- namespace detail { /** @class AssembleArgv @@ -272,7 +329,7 @@ namespace xo { template <> struct AssembleArgv<> { - static void append_argv(std::vector * p_v) {} + static void append_argv(std::vector * /*p_v*/) {} }; template @@ -284,6 +341,28 @@ namespace xo { }; } /*detail*/ + template + std::unique_ptr + EstablishFunctionTdx::make() { + std::vector argv; + detail::AssembleArgv::append_argv(&argv); + + return FunctionTdx::make_function(Reflect::require(), + std::move(argv), + false /*!is_noexcept*/); + } + + template + std::unique_ptr + EstablishFunctionTdx::make() { + std::vector argv; + detail::AssembleArgv::append_argv(&argv); + + return FunctionTdx::make_function(Reflect::require(), + std::move(argv), + true /*is_noexcept*/); + } + /* declared above before * class Reflect { ... } */ @@ -293,8 +372,25 @@ namespace xo { std::vector argv; detail::AssembleArgv::append_argv(&argv); - return FunctionTdx::make_function(Reflect::require(), std::move(argv)); + return FunctionTdx::make_function(Reflect::require(), + std::move(argv), + false /*!is_noexcept*/); } + + /* declared above before + * class Reflect { ... } + */ + template + std::unique_ptr + EstablishTdx::make() { + std::vector argv; + detail::AssembleArgv::append_argv(&argv); + + return FunctionTdx::make_function(Reflect::require(), + std::move(argv), + true /*is_noexcept*/); + } + } /*namespace reflect*/ } /*namespace xo*/ diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index c50a6d8d..54013654 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -226,6 +226,7 @@ namespace xo { */ TypeDescr fn_retval() const { return this->tdextra_->fn_retval(); } TypeDescr fn_arg(uint32_t i) const { return this->tdextra_->fn_arg(i); } + bool fn_is_noexcept() const { return this->tdextra_->fn_is_noexcept(); } void display(std::ostream & os) const; std::string display_string() const; diff --git a/include/xo/reflect/TypeDescrExtra.hpp b/include/xo/reflect/TypeDescrExtra.hpp index 54db3025..c16257b3 100644 --- a/include/xo/reflect/TypeDescrExtra.hpp +++ b/include/xo/reflect/TypeDescrExtra.hpp @@ -67,9 +67,10 @@ namespace xo { * * @pre @ref TypeDescrExtra::is_function() is true **/ - virtual uint32_t n_fn_arg() const { return 0; } virtual const TypeDescrBase * fn_retval() const { return nullptr; } + virtual uint32_t n_fn_arg() const { return 0; } virtual const TypeDescrBase * fn_arg(uint32_t /*i_arg*/) const { return nullptr; } + virtual bool fn_is_noexcept() const { return false; } }; /*TypeDescrExtra*/ } /*namespace reflect*/ } /*namespace xo*/ diff --git a/include/xo/reflect/function/FunctionTdx.hpp b/include/xo/reflect/function/FunctionTdx.hpp index 1558328d..9c5523a6 100644 --- a/include/xo/reflect/function/FunctionTdx.hpp +++ b/include/xo/reflect/function/FunctionTdx.hpp @@ -15,11 +15,15 @@ namespace xo { public: virtual ~FunctionTdx() = default; - /** create instance. Will be invoked exactly once for each reflected function type **/ + /** create instance. Will be invoked exactly once for each reflected function type + * + * @param retval_td. type description for return value + * @param arg_td_v. type descriptions for arguments, in positional order + * @param is_noexcept. true iff function marked noexcept + **/ static std::unique_ptr make_function(TypeDescr retval_td, - std::vector arg_td_v); - - + std::vector arg_td_v, + bool is_noexcept); // ----- Inherited from TypeDescrExtra ----- @@ -28,12 +32,14 @@ namespace xo { virtual TaggedPtr child_tp(uint32_t i, void * object) const override; const std::string & struct_member_name(uint32_t i) const override; - virtual uint32_t n_fn_arg() const override { return arg_td_v_.size(); } virtual TypeDescr fn_retval() const override { return retval_td_; } + virtual uint32_t n_fn_arg() const override { return arg_td_v_.size(); } virtual TypeDescr fn_arg(uint32_t i) const override { return arg_td_v_[i]; } + virtual bool fn_is_noexcept() const override { return is_noexcept_; } private: FunctionTdx(TypeDescr retval_td, + bool is_noexcept, std::vector arg_td_v); private: @@ -41,6 +47,8 @@ namespace xo { TypeDescr retval_td_; /** function arguments, in positional order **/ std::vector arg_td_v_; + /** true iff function promises never to throw **/ + bool is_noexcept_ = false; }; /*FunctionTdx*/ } /*namespace reflect*/ } /*namespace xo*/ diff --git a/src/reflect/function/FunctionTdx.cpp b/src/reflect/function/FunctionTdx.cpp index 1b715fcd..bfd43673 100644 --- a/src/reflect/function/FunctionTdx.cpp +++ b/src/reflect/function/FunctionTdx.cpp @@ -5,19 +5,28 @@ namespace xo { namespace reflect { - /** create instance. Will be invoked exactly once for each reflected function type **/ + /** create instance. Will be invoked exactly once for each reflected function type **/ std::unique_ptr FunctionTdx::make_function(TypeDescr retval_td, - std::vector arg_td_v) + std::vector arg_td_v, + bool is_noexcept) { - return std::unique_ptr(new FunctionTdx(retval_td, std::move(arg_td_v))); + return std::unique_ptr(new FunctionTdx(retval_td, + is_noexcept, + std::move(arg_td_v))); } FunctionTdx::FunctionTdx(TypeDescr retval_td, + bool is_noexcept, std::vector arg_td_v) : retval_td_{retval_td}, - arg_td_v_{std::move(arg_td_v)} - {} + arg_td_v_{std::move(arg_td_v)}, + is_noexcept_{is_noexcept} + { + if (!retval_td) { + throw std::runtime_error("FunctionTdx::ctor: null return type?"); + } + } TaggedPtr FunctionTdx::child_tp(uint32_t /*i*/, void * /*object*/) const From fa0104422fc7330f3a71aaa691be5bb5716eaf5b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 13:17:14 -0400 Subject: [PATCH 1042/2524] xo-jit: incorporate kaleidoscope jit for codegen --- example/ex_kaleidoscope4/CMakeLists.txt | 12 ++ example/ex_kaleidoscope4/ex_kaleidoscope4.cpp | 16 +++ include/xo/jit/Jit.hpp | 50 ++++++- include/xo/jit/KaleidoscopeJit.hpp | 126 ++++++++++++++++++ src/jit/Jit.cpp | 126 ++++++++++++++++-- 5 files changed, 319 insertions(+), 11 deletions(-) create mode 100644 example/ex_kaleidoscope4/CMakeLists.txt create mode 100644 example/ex_kaleidoscope4/ex_kaleidoscope4.cpp create mode 100644 include/xo/jit/KaleidoscopeJit.hpp diff --git a/example/ex_kaleidoscope4/CMakeLists.txt b/example/ex_kaleidoscope4/CMakeLists.txt new file mode 100644 index 00000000..4e9476c3 --- /dev/null +++ b/example/ex_kaleidoscope4/CMakeLists.txt @@ -0,0 +1,12 @@ +# xo-jit/example/ex1/CMakeLists.txt + +set(SELF_EXE xo_kaleidoscope4) +set(SELF_SRCS ex_kaleidoscope4.cpp) + +if (XO_ENABLE_EXAMPLES) + xo_add_executable(${SELF_EXE} ${SELF_SRCS}) + xo_self_dependency(${SELF_EXE} xo_jit) + #xo_dependency(${SELF_EXE} xo_expression) +endif() + +# end CMakeLists.txt diff --git a/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp b/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp new file mode 100644 index 00000000..63926aad --- /dev/null +++ b/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp @@ -0,0 +1,16 @@ +/** ex_kaleidoscop4.cpp **/ + +#include "xo/jit/KaleidoscopeJit.hpp" +#include + +int +main() { + using std::cerr; + using std::endl; + + auto jit = xo::jit::KaleidoscopeJIT::Create(); + + cerr << "created kaleidoscope jit successfully" << endl; +} + +/** end ex_kaleidoscope4.cpp **/ diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 044343b8..d66f86fe 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -14,6 +14,27 @@ #include "xo/expression/Apply.hpp" #include "xo/expression/Lambda.hpp" #include "xo/expression/Variable.hpp" + +#include "KaleidoscopeJit.hpp" +#ifdef NOT_USING +/* stuff from KaleidoscopeJIT.hpp */ +#include "llvm/ADT/StringRef.h" +#include "llvm/ExecutionEngine/JITSymbol.h" +#include "llvm/ExecutionEngine/Orc/CompileUtils.h" +#include "llvm/ExecutionEngine/Orc/Core.h" +#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" +#include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h" +#include "llvm/ExecutionEngine/Orc/IRCompileLayer.h" +#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h" +#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" +#include "llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h" +#include "llvm/ExecutionEngine/SectionMemoryManager.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/LLVMContext.h" +#include +#endif + +/* stuff from kaleidoscope.cpp */ #include "llvm/ADT/APFloat.h" #include "llvm/ADT/STLExtras.h" #include "llvm/IR/BasicBlock.h" @@ -36,6 +57,7 @@ #include "llvm/Transforms/Scalar/Reassociate.h" #include "llvm/Transforms/Scalar/SimplifyCFG.h" + namespace xo { namespace jit { /** @class Jit @@ -49,7 +71,8 @@ namespace xo { //using ConstantInterface = xo::ast::ConstantInterface; public: - static ref::rp make() { return new Jit(); } + /* tracking KaleidoscopeJIT::Create() here.. */ + static llvm::Expected> make_aux(); llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); @@ -63,9 +86,32 @@ namespace xo { virtual std::string display_string() const; private: - Jit(); + Jit( + std::unique_ptr kal_jit +#ifdef NOT_USING + std::unique_ptr es, + llvm::orc::JITTargetMachineBuilder jtmb, + llvm::DataLayout dl +#endif + ); private: + // ----- this part adapted from LLVM 19.0 KaleidoscopeJIT.hpp [wip] ----- + + std::unique_ptr kal_jit_; + +#ifdef NOT_USING + std::unique_ptr jit_es_; + llvm::DataLayout jit_data_layout_; + llvm::orc::MangleAndInterner jit_mangle_; + llvm::orc::RTDyldObjectLinkingLayer jit_object_layer_; + llvm::orc::IRCompileLayer jit_compile_layer_; + /** reference here. looks like storage owned by .jit_es **/ + llvm::orc::JITDylib & jit_our_dynamic_lib_; +#endif + + // ----- this part adapted from kaleidoscope.cpp ----- + /** owns + manages core "global" llvm data, * including type- and constant- unique-ing tables. * diff --git a/include/xo/jit/KaleidoscopeJit.hpp b/include/xo/jit/KaleidoscopeJit.hpp new file mode 100644 index 00000000..3094c076 --- /dev/null +++ b/include/xo/jit/KaleidoscopeJit.hpp @@ -0,0 +1,126 @@ +//===- KaleidoscopeJIT.h - A simple JIT for Kaleidoscope --------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Contains a simple JIT definition for use in the kaleidoscope tutorials. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H +#define LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/ExecutionEngine/JITSymbol.h" +#include "llvm/ExecutionEngine/Orc/CompileUtils.h" +#include "llvm/ExecutionEngine/Orc/Core.h" +#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" +#include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h" +#include "llvm/ExecutionEngine/Orc/IRCompileLayer.h" +#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h" +#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" +#include "llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h" +#include "llvm/ExecutionEngine/SectionMemoryManager.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/LLVMContext.h" +#include + +namespace xo { + namespace jit { + + class KaleidoscopeJIT { + private: + using StringRef = llvm::StringRef; + using SectionMemoryManager = llvm::SectionMemoryManager; + using DynamicLibrarySearchGenerator = llvm::orc::DynamicLibrarySearchGenerator; + using ConcurrentIRCompiler = llvm::orc::ConcurrentIRCompiler; + using ExecutionSession = llvm::orc::ExecutionSession; + using DataLayout = llvm::DataLayout; + using MangleAndInterner = llvm::orc::MangleAndInterner; + using RTDyldObjectLinkingLayer = llvm::orc::RTDyldObjectLinkingLayer; + using IRCompileLayer = llvm::orc::IRCompileLayer; + using JITDylib = llvm::orc::JITDylib; + using JITTargetMachineBuilder = llvm::orc::JITTargetMachineBuilder; + using ThreadSafeModule = llvm::orc::ThreadSafeModule; + using ResourceTrackerSP = llvm::orc::ResourceTrackerSP; + using ExecutorSymbolDef = llvm::orc::ExecutorSymbolDef; + using SelfExecutorProcessControl = llvm::orc::SelfExecutorProcessControl; + + private: + std::unique_ptr ES; + + DataLayout DL; + MangleAndInterner Mangle; + + RTDyldObjectLinkingLayer ObjectLayer; + IRCompileLayer CompileLayer; + + JITDylib &MainJD; + + public: + KaleidoscopeJIT(std::unique_ptr ES, + JITTargetMachineBuilder JTMB, + DataLayout DL) + : ES(std::move(ES)), + DL(std::move(DL)), + Mangle(*this->ES, this->DL), + ObjectLayer(*this->ES, + []() { return std::make_unique(); }), + CompileLayer(*this->ES, ObjectLayer, + std::make_unique(std::move(JTMB))), + MainJD(this->ES->createBareJITDylib("
")) + { + MainJD.addGenerator( + cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess( + DL.getGlobalPrefix()))); + if (JTMB.getTargetTriple().isOSBinFormatCOFF()) { + ObjectLayer.setOverrideObjectFlagsWithResponsibilityFlags(true); + ObjectLayer.setAutoClaimResponsibilityForObjectSymbols(true); + } + } + + ~KaleidoscopeJIT() { + if (auto Err = ES->endSession()) + ES->reportError(std::move(Err)); + } + + static llvm::Expected> Create() { + auto EPC = SelfExecutorProcessControl::Create(); + if (!EPC) + return EPC.takeError(); + + auto ES = std::make_unique(std::move(*EPC)); + + JITTargetMachineBuilder JTMB( + ES->getExecutorProcessControl().getTargetTriple()); + + auto DL = JTMB.getDefaultDataLayoutForTarget(); + if (!DL) + return DL.takeError(); + + return std::make_unique(std::move(ES), std::move(JTMB), + std::move(*DL)); + } + + const DataLayout &getDataLayout() const { return DL; } + + JITDylib &getMainJITDylib() { return MainJD; } + + llvm::Error addModule(ThreadSafeModule TSM, ResourceTrackerSP RT = nullptr) { + if (!RT) + RT = MainJD.getDefaultResourceTracker(); + return CompileLayer.add(RT, std::move(TSM)); + } + + llvm::Expected lookup(StringRef Name) { + return ES->lookup({&MainJD}, Mangle(Name.str())); + } + }; + + } // end namespace jit +} // end namespace xo + +#endif // LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 872b949e..d5aa2901 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -15,11 +15,96 @@ namespace xo { using std::endl; namespace jit { - Jit::Jit() - : llvm_cx_{std::make_unique()}, + + /* tracking KaleidoscopeJIT::Create() here.. + * + * Verified: + * + 'execution session' as per Kaleidoscope JIT, + * can instantiate from python + * + 'jit object layer' + * (realtime dynamic library object linking layer) + * + 'jit_copmile_layer' + * + 'jit_our_dynamic_lib' + */ + llvm::Expected> + Jit::make_aux() + { +#ifdef NOT_USING + /* 'executor process control' */ + auto epc = llvm::orc::SelfExecutorProcessControl::Create(); + if (!epc) { + return epc.takeError(); + //throw std::runtime_error("Jit::make: failed to establish executor process control"); + } + + /* 'execution session' */ + auto es = std::make_unique(std::move(*epc)); + + /* 'jit target machine builder' */ + llvm::orc::JITTargetMachineBuilder jtmb(es + ->getExecutorProcessControl() + .getTargetTriple()); + + auto dl = jtmb.getDefaultDataLayoutForTarget(); + if (!dl) { + return dl.takeError(); + //throw std::runtime_error("Jit::make: failed to establish data layout object" + // " for target machine"); + } +#endif + + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr kal_jit = llvm_exit_on_err(KaleidoscopeJIT::Create()); + + return std::unique_ptr(new Jit(std::move(kal_jit) +#ifdef NOT_USING + std::move(es), + std::move(jtmb), + std::move(*dl) +#endif + )); + } /*make*/ + + Jit::Jit( + std::unique_ptr kal_jit +#ifdef NOT_USING + std::unique_ptr jit_es, + llvm::orc::JITTargetMachineBuilder jtmb, + llvm::DataLayout dl +#endif + ) + : kal_jit_{std::move(kal_jit)}, +#ifdef NOT_USING + jit_es_(std::move(jit_es)), + jit_data_layout_(std::move(dl)), + jit_mangle_(*this->jit_es_, this->jit_data_layout_), + jit_object_layer_(*this->jit_es_, + []() { return std::make_unique(); }), + jit_compile_layer_(*this->jit_es_, + jit_object_layer_, + std::make_unique(std::move(jtmb))), + /* note: string passed to createBareJITDyLib must be unique + * (within running process address space?) + */ + jit_our_dynamic_lib_(this->jit_es_->createBareJITDylib("")), /*was MainJD*/ +#endif + + llvm_cx_{std::make_unique()}, llvm_ir_builder_{std::make_unique>(*llvm_cx_)}, llvm_module_{std::make_unique("xojit", *llvm_cx_)} - {} + { +#ifdef NOT_USING + jit_our_dynamic_lib_.addGenerator + (cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess + (jit_data_layout_.getGlobalPrefix()))); + + if(jtmb.getTargetTriple().isOSBinFormatCOFF()) { + jit_object_layer_.setOverrideObjectFlagsWithResponsibilityFlags(true); + jit_object_layer_.setAutoClaimResponsibilityForObjectSymbols(true); + } +#endif + } llvm::Value * Jit::codegen_constant(ref::brw expr) @@ -40,9 +125,12 @@ namespace xo { llvm::Function * Jit::codegen_primitive(ref::brw expr) { + constexpr bool c_debug_flag = true; + using xo::scope; + /** note: documentation (such as it is) for llvm::Function here: * - * https://llvm.org/doxygen/classllvm_1_1Function.html + * https://llvm.org/doxygenL/classllvm_1_1Function.html **/ auto * fn = llvm_module_->getFunction(expr->name()); @@ -64,12 +152,19 @@ namespace xo { TypeDescr fn_td = expr->value_td(); int n_fn_arg = fn_td->n_fn_arg(); - std::vector llvm_argtype_v(n_fn_arg); + scope log(XO_DEBUG(c_debug_flag), + xtag("fn_td", fn_td->short_name()), + xtag("n_fn_arg", n_fn_arg)); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(n_fn_arg); /** check function args are all doubles **/ for (int i = 0; i < n_fn_arg; ++i) { TypeDescr arg_td = fn_td->fn_arg(i); + log && log(xtag("i_arg", i), xtag("arg_td", arg_td->short_name())); + if (arg_td->is_native()) { llvm_argtype_v.push_back(llvm::Type::getDoubleTy(*llvm_cx_)); @@ -87,6 +182,9 @@ namespace xo { //std::vector double_v(n_fn_arg, llvm::Type::getDoubleTy(*llvm_cx_)); TypeDescr retval_td = fn_td->fn_retval(); + + log && log(xtag("retval_td", retval_td->short_name())); + llvm::Type * llvm_retval = nullptr; if (retval_td->is_native()) { @@ -108,12 +206,22 @@ namespace xo { expr->name(), llvm_module_.get()); - // set names for arguments (for diagonostics?). Money-see-kaleidoscope-monkey-do here #ifdef NOT_USING - for (auto & arg : fn->args()) - arg.setName(formalnameofthisarg); + // set names for arguments (for diagnostics?). Monkey-see-kaleidoscope-monkey-do here + { + int i_arg = 0; + for (auto & arg : fn->args()) { + std::stringstream ss; + ss << "x_" << i_arg; + + arg.setName(ss.str()); + ++i_arg; + } + } #endif + log && log("returning llvm function"); + return fn; } /*codegen_primitive*/ @@ -189,7 +297,7 @@ namespace xo { /* establish prototype for this function */ // PLACEHOLDER - // just make prototype for function :: double -> double + // just handle double arguments + return type for now std::vector double_v(1, llvm::Type::getDoubleTy(*llvm_cx_)); From 425323f917173bca5a75810d41638811fd5fe0c9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 13:17:31 -0400 Subject: [PATCH 1043/2524] xo-jit: ++ extend example --- example/CMakeLists.txt | 1 + example/ex1/ex1.cpp | 49 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 4151ec21..0cb5ee51 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(ex1) +add_subdirectory(ex_kaleidoscope4) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index d86b77f4..01b914d8 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -8,8 +8,33 @@ #include "xo/expression/Variable.hpp" #include +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/PassManager.h" +#include "llvm/IR/Type.h" +#include "llvm/IR/Verifier.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/StandardInstrumentations.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Target/TargetMachine.h" +#include "llvm/Transforms/InstCombine/InstCombine.h" +#include "llvm/Transforms/Scalar.h" +#include "llvm/Transforms/Scalar/GVN.h" +#include "llvm/Transforms/Scalar/Reassociate.h" +#include "llvm/Transforms/Scalar/SimplifyCFG.h" + +//double foo(double x) { return x; } + int main() { + using xo::scope; using xo::jit::Jit; using xo::ast::make_constant; using xo::ast::make_primitive; @@ -22,11 +47,23 @@ main() { //using xo::ast::make_constant; - auto jit = Jit::make(); + static llvm::ExitOnError llvm_exit_on_err; + + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); + + auto jit = llvm_exit_on_err(Jit::make_aux()); + + //static_assert(std::is_function_v); + + scope log(XO_DEBUG(true)); { auto expr = make_constant(7.0); + log && log(xtag("expr", expr)); + auto llvm_ircode = jit->codegen(expr); if (llvm_ircode) { @@ -42,7 +79,9 @@ main() { } { - auto expr = make_primitive("sqrt", ::sqrt); + auto expr = make_primitive("sqrt", &sqrt); + + log && log(xtag("expr", expr)); auto llvm_ircode = jit->codegen(expr); @@ -61,11 +100,13 @@ main() { { /* (sqrt 2) */ - auto fn = make_primitive("sqrt", ::sqrt); + auto fn = make_primitive("sqrt", &sqrt); auto arg = make_constant(2.0); auto call = make_apply(fn, arg); + log && log(xtag("expr", call)); + auto llvm_ircode = jit->codegen(call); if (llvm_ircode) { @@ -95,6 +136,8 @@ main() { {"x"}, call2); + log && log(xtag("expr", lambda)); + auto llvm_ircode = jit->codegen(lambda); if (llvm_ircode) { From 066c1356292d46d5bf5b8e8b6c37304bf98ec9ba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:11:10 -0400 Subject: [PATCH 1044/2524] xo-jit: bugfix: need release on unique_ptr -> xfer to refcounted ptr --- example/ex1/ex1.cpp | 3 ++- include/xo/jit/Jit.hpp | 2 ++ src/jit/Jit.cpp | 9 +++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 01b914d8..04dc2f12 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -53,7 +53,8 @@ main() { llvm::InitializeNativeTargetAsmPrinter(); llvm::InitializeNativeTargetAsmParser(); - auto jit = llvm_exit_on_err(Jit::make_aux()); + //auto jit = llvm_exit_on_err(Jit::make_aux()); + auto jit = Jit::make(); //static_assert(std::is_function_v); diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index d66f86fe..26c81e05 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -73,6 +73,8 @@ namespace xo { public: /* tracking KaleidoscopeJIT::Create() here.. */ static llvm::Expected> make_aux(); + static xo::ref::rp make(); + llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index d5aa2901..66412b67 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -66,6 +66,15 @@ namespace xo { )); } /*make*/ + xo::ref::rp + Jit::make() { + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr jit = llvm_exit_on_err(make_aux()); + + return jit.release(); + } /*make*/ + Jit::Jit( std::unique_ptr kal_jit #ifdef NOT_USING From e8a2297ac0c705375a95db9ca50e404e25c336de Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:11:59 -0400 Subject: [PATCH 1045/2524] xo-jit: need global initialization as per kaleidoscope4 --- include/xo/jit/Jit.hpp | 3 +++ src/jit/Jit.cpp | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 26c81e05..57e8cf9e 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -97,6 +97,9 @@ namespace xo { #endif ); + /* iniitialize native builder (i.e. for platform we're running on) */ + static void init_once(); + private: // ----- this part adapted from LLVM 19.0 KaleidoscopeJIT.hpp [wip] ----- diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 66412b67..6a2d43a5 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -15,6 +15,18 @@ namespace xo { using std::endl; namespace jit { + void + Jit::init_once() { + static bool s_init_once = false; + + if (!s_init_once) { + s_init_once = true; + + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); + } + } /*init_once*/ /* tracking KaleidoscopeJIT::Create() here.. * @@ -29,6 +41,8 @@ namespace xo { llvm::Expected> Jit::make_aux() { + Jit::init_once(); + #ifdef NOT_USING /* 'executor process control' */ auto epc = llvm::orc::SelfExecutorProcessControl::Create(); From 3b5193c28d97a14528d1e407211ccc9c452ddf8b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:12:52 -0400 Subject: [PATCH 1046/2524] xo-jit: + llvm module instrumentation methods --- include/xo/jit/Jit.hpp | 11 +++++++++++ include/xo/jit/KaleidoscopeJit.hpp | 6 ++++++ src/jit/Jit.cpp | 17 +++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 57e8cf9e..43a289d4 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -75,6 +75,17 @@ namespace xo { static llvm::Expected> make_aux(); static xo::ref::rp make(); + // ----- module access ----- + + /** target triple = string describing target host for codegen **/ + const std::string & target_triple() const; + /** append function names defined in attached module to *p_v **/ + std::vector get_function_name_v(); + + /** write state of execution session (all the associated dynamic libraries) **/ + void dump_execution_session(); + + // ----- jit code generation ----- llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); diff --git a/include/xo/jit/KaleidoscopeJit.hpp b/include/xo/jit/KaleidoscopeJit.hpp index 3094c076..89f1a724 100644 --- a/include/xo/jit/KaleidoscopeJit.hpp +++ b/include/xo/jit/KaleidoscopeJit.hpp @@ -50,6 +50,7 @@ namespace xo { using SelfExecutorProcessControl = llvm::orc::SelfExecutorProcessControl; private: + /* execution session - represents a currenlty-running jit program */ std::unique_ptr ES; DataLayout DL; @@ -118,6 +119,11 @@ namespace xo { llvm::Expected lookup(StringRef Name) { return ES->lookup({&MainJD}, Mangle(Name.str())); } + + /* dump */ + void dump_execution_session() { + ES->dump(llvm::errs()); + } }; } // end namespace jit diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 6a2d43a5..4591fadf 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -127,6 +127,23 @@ namespace xo { jit_object_layer_.setAutoClaimResponsibilityForObjectSymbols(true); } #endif + const std::string & + Jit::target_triple() const { + return llvm_module_->getTargetTriple(); + } + + std::vector + Jit::get_function_name_v() { + std::vector retval; + for (const auto & fn_name : *llvm_module_) + retval.push_back(fn_name.getName().str()); + + return retval; + } /*get_function_names*/ + + void + Jit::dump_execution_session() { + kal_jit_->dump_execution_session(); } llvm::Value * From 2b1827f5ccc23a9939cada1e9978e06a3e778c29 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:13:25 -0400 Subject: [PATCH 1047/2524] xo-jit: bugfix: missed } --- src/jit/Jit.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 4591fadf..ec583c7c 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -127,6 +127,8 @@ namespace xo { jit_object_layer_.setAutoClaimResponsibilityForObjectSymbols(true); } #endif + } + const std::string & Jit::target_triple() const { return llvm_module_->getTargetTriple(); From 38d9609147aec3747bc6fc9c888fbb0d371aadd4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:13:44 -0400 Subject: [PATCH 1048/2524] xo-jit: set data layout on llvm module as per kaleidoscope4 --- src/jit/Jit.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index ec583c7c..640407f5 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -127,6 +127,9 @@ namespace xo { jit_object_layer_.setAutoClaimResponsibilityForObjectSymbols(true); } #endif + + llvm_module_->setDataLayout(kal_jit_->getDataLayout()); + } const std::string & From cf7bc43c5e41c6a9af63f0edd420f82126e71f9b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:14:00 -0400 Subject: [PATCH 1049/2524] xo-jit: check non-null support pointers --- src/jit/Jit.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 640407f5..20d3a7cd 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -130,6 +130,15 @@ namespace xo { llvm_module_->setDataLayout(kal_jit_->getDataLayout()); + if (!llvm_cx_.get()) { + throw std::runtime_error("Jit::ctor: expected non-empty llvm context"); + } + if (!llvm_ir_builder_.get()) { + throw std::runtime_error("Jit::ctor: expected non-empty llvm IR builder"); + } + if (!llvm_module_.get()) { + throw std::runtime_error("Jit::ctor: expected non-empty llvm module"); + } } const std::string & From f7dfd67770bc8dda32997241fa0e18063db28407 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:14:22 -0400 Subject: [PATCH 1050/2524] xo-jit: + lookup_symbol method on dynamic library --- include/xo/jit/Jit.hpp | 2 ++ src/jit/Jit.cpp | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 43a289d4..cf3f3a0f 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -95,6 +95,8 @@ namespace xo { llvm::Value * codegen(ref::brw expr); + llvm::orc::ExecutorAddr lookup_symbol(const std::string & x); + virtual void display(std::ostream & os) const; virtual std::string display_string() const; diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 20d3a7cd..eef5c197 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -442,6 +442,20 @@ namespace xo { return nullptr; } /*codegen*/ + llvm::orc::ExecutorAddr + Jit::lookup_symbol(const std::string & sym) + { + static llvm::ExitOnError llvm_exit_on_err; + + /* llvm_sym: ExecutorSymbolDef */ + auto llvm_sym = llvm_exit_on_err(kal_jit_->lookup(sym)); + + /* llvm_addr: ExecutorAddr */ + auto llvm_addr = llvm_sym.getAddress(); + + return llvm_addr; + } /*lookup_symbol*/ + void Jit::display(std::ostream & os) const { os << ""; From dafa567eaa8ce10fce989fb8d4869c97cbff9c9e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:15:07 -0400 Subject: [PATCH 1051/2524] xo-pyjit: invoke pybind11 stl support --- src/pyjit/pyjit.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index db202824..5f233fff 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -4,6 +4,7 @@ #include "xo/pyexpression/pyexpression.hpp" #include "xo/jit/Jit.hpp" #include "xo/pyutil/pyutil.hpp" +#include namespace xo { namespace jit { From 7402746dbd0f1e224d7a537f6b2675c1147da361 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:15:22 -0400 Subject: [PATCH 1052/2524] xo-pyjit: expose Jit instrumentation getters --- src/pyjit/pyjit.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 5f233fff..2bd2afaf 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -24,6 +24,12 @@ namespace xo { py::doc("create Jit instance. Not threadsafe," " but does not share resources with any other Jit instance")) + .def_property_readonly("target_triple", &Jit::target_triple, + py::doc("string describing target host for code generation")) + .def("get_function_name_v", &Jit::get_function_name_v, + py::doc("get vector of function names defined in jit module")) + .def("dump_execution_session", &Jit::dump_execution_session, + py::doc("write to console with state of all jit-owned dynamic libraries")) .def("codegen", [](Jit & jit, const rp & expr) { return jit.codegen(expr.borrow()); From 01e0b8ffbafe55f9ceb632304885614ea158dfdf Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:15:45 -0400 Subject: [PATCH 1053/2524] xo-pyjit: + XferFn for symbol lookup [wip] --- src/pyjit/pyjit.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 2bd2afaf..14bfc9f4 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -7,6 +7,16 @@ #include namespace xo { + struct XferFn : public ref::Refcount { + using fptr_type = double (*) (double); + + explicit XferFn(fptr_type fptr) : fptr_{fptr} {} + + double operator() (double x) { return (*fptr_)(x); } + + fptr_type fptr_; + }; + namespace jit { using xo::ast::Expression; using xo::ref::rp; @@ -42,6 +52,10 @@ namespace xo { * RC 14jun2024 - I think this is true modulo use of llvm resource trackers. */ py::return_value_policy::reference_internal) + py::class_>(m, "XferFn") + .def("__call__", + [](XferFn & self, double x) { return self(x); } + ) ; py::class_ Date: Sat, 15 Jun 2024 15:16:00 -0400 Subject: [PATCH 1054/2524] xo-pyjit: + lookup_dbl_dbl_fn [wip -- not actually working yet] --- src/pyjit/pyjit.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 14bfc9f4..c69558a1 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -49,9 +49,19 @@ namespace xo { /* we're assuming llvm-generated code lives for as long as the Jit * instance that created it. * - * RC 14jun2024 - I think this is true modulo use of llvm resource trackers. + * RC 14jun2024 - I think this is true, modulo use of llvm resource trackers. */ py::return_value_policy::reference_internal) + .def("lookup_dbl_dbl_fn", + [](Jit & jit, const std::string & symbol) { + auto llvm_addr = jit.lookup_symbol(symbol); + + auto fn_addr = llvm_addr.toPtr(); + + return new XferFn(fn_addr); + }) + ; + py::class_>(m, "XferFn") .def("__call__", [](XferFn & self, double x) { return self(x); } From f7db84972f458bcd7f7caa41d297aa60dae7d304 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 16:02:56 -0400 Subject: [PATCH 1055/2524] xo-jit: setup analsysis pipeline (most of kaleidoscope4) --- include/xo/jit/Jit.hpp | 24 +++++++++++++++++++++++- src/jit/Jit.cpp | 27 +++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index cf3f3a0f..3a8e4c14 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -79,7 +79,10 @@ namespace xo { /** target triple = string describing target host for codegen **/ const std::string & target_triple() const; - /** append function names defined in attached module to *p_v **/ + /** append function names defined in attached module to *p_v + * + * (RC 15jun2024 - this part is working) + **/ std::vector get_function_name_v(); /** write state of execution session (all the associated dynamic libraries) **/ @@ -148,8 +151,27 @@ namespace xo { std::map> global_env_; /** map variable names (formal parameters) to * corresponding llvm interactor + * + * only supports one level atm (i.e. only top-level functions) **/ std::map nested_env_; + + // ----- transforms (also adapted from kaleidescope.cpp) ------ + + /** manages all the passes+analaysis (?) **/ + std::unique_ptr llvm_fpmgr_; + /** loop analysis (?) **/ + std::unique_ptr llvm_lamgr_; + /** function-level analysis (?) **/ + std::unique_ptr llvm_famgr_; + /** cgscc (?) analysis **/ + std::unique_ptr llvm_cgamgr_; + /** module analsyis (?) **/ + std::unique_ptr llvm_mamgr_; + /** pass instrumentation **/ + std::unique_ptr llvm_pic_; + /** standard instrumentation **/ + std::unique_ptr llvm_si_; }; /*Jit*/ inline std::ostream & diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index eef5c197..7d53a313 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -67,6 +67,7 @@ namespace xo { } #endif + static llvm::ExitOnError llvm_exit_on_err; std::unique_ptr kal_jit = llvm_exit_on_err(KaleidoscopeJIT::Create()); @@ -139,6 +140,28 @@ namespace xo { if (!llvm_module_.get()) { throw std::runtime_error("Jit::ctor: expected non-empty llvm module"); } + + this->llvm_fpmgr_ = std::make_unique(); + this->llvm_lamgr_ = std::make_unique(); + this->llvm_famgr_ = std::make_unique(); + this->llvm_cgamgr_ = std::make_unique(); + this->llvm_mamgr_ = std::make_unique(); + this->llvm_pic_ = std::make_unique(); + this->llvm_si_ = std::make_unique(*llvm_cx_, + /*DebugLogging*/ true); + + this->llvm_si_->registerCallbacks(*llvm_pic_, llvm_mamgr_.get()); + + // TODO: llvm_fpmgr_->addPass(InstCombinePass()) etc. + // TODO: llvm_fpmgr_->addPass(ReassociatePass()) etc. + // TODO: llvm_fpmgr_->addPass(GVNPasss()) etc. + // TODO: llvm_fpmgr_->addPass(SimplifyCFGPass()) etc. + + /** tracking for analysis passes that share info? **/ + llvm::PassBuilder llvm_pass_builder; + llvm_pass_builder.registerModuleAnalyses(*llvm_mamgr_); + llvm_pass_builder.registerFunctionAnalyses(*llvm_famgr_); + llvm_pass_builder.crossRegisterProxies(*llvm_lamgr_, *llvm_famgr_, *llvm_cgamgr_, *llvm_mamgr_); } const std::string & @@ -389,8 +412,8 @@ namespace xo { /* validate! always validate! */ llvm::verifyFunction(*fn); - /* optimize! */ - // thefpm->run(*fn, *thefam); + /* optimize! does this generate code? */ + llvm_fpmgr_->run(*fn, *llvm_famgr_); return fn; } From 7f4d0ee8e29022e2ae46d0ebbbeba5820257a20b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 16:03:19 -0400 Subject: [PATCH 1056/2524] xo-jit: README: ++ links --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index e2c4cbb3..a8a7457d 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ A library for representing abstract syntax trees for EGAD (a small jit-based language). +## Links + +- [new pass manager (26mar2021)](https://blog.llvm.org/posts/2021-03-26-the-new-pass-manager) +- [life of an llvm instruction (24nov2012)](https://eli.thegreenplace.net/2012/11/24/life-of-an-instruction-in-llvm) + ## Getting Started ### build + install `xo-cmake` dependency From 73bf0663188117d291ac36eb581690e8a60f3cc1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 16:03:28 -0400 Subject: [PATCH 1057/2524] xo-jit: ex1: dump execution session --- example/ex1/ex1.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 04dc2f12..77ee265c 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -151,6 +151,10 @@ main() { << xtag("expr", lambda) << endl; } + + /* is this in module? */ + cerr << "ex1: jit execution session" << endl; + jit->dump_execution_session(); } } From 1b3718bd12a48775724446dd1475d0d1d1d33d65 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 17:13:59 -0400 Subject: [PATCH 1058/2524] xo-jit: + machgen_current_module(): generate machine code! --- include/xo/jit/Jit.hpp | 22 +++++++++++++++++++++- src/jit/Jit.cpp | 32 +++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 3a8e4c14..14b368d3 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -98,6 +98,13 @@ namespace xo { llvm::Value * codegen(ref::brw expr); + // ----- jit online execution ----- + + /** add IR code in current module to JIT, + * so that its available for execution + **/ + void machgen_current_module(); + llvm::orc::ExecutorAddr lookup_symbol(const std::string & x); virtual void display(std::ostream & os) const; @@ -116,6 +123,9 @@ namespace xo { /* iniitialize native builder (i.e. for platform we're running on) */ static void init_once(); + /** (re)create pipeline to turn expressions into llvm IR code **/ + void recreate_llvm_ir_pipeline(); + private: // ----- this part adapted from LLVM 19.0 KaleidoscopeJIT.hpp [wip] ----- @@ -133,6 +143,14 @@ namespace xo { // ----- this part adapted from kaleidoscope.cpp ----- + /** everything bleow represents a pipeline + * that takes expressions, and turns them into llvm IR. + * + * llvm IR can be added to running JIT by calling + * kal_jit_.addModule() + * Note that this makes the module itself unavailable to us + **/ + /** owns + manages core "global" llvm data, * including type- and constant- unique-ing tables. * @@ -142,7 +160,9 @@ namespace xo { std::unique_ptr llvm_cx_; /** builder for intermediate-representation objects **/ std::unique_ptr> llvm_ir_builder_; - /** a module (aka library) being prepared by llvm. + /** a module (1:1 with library) being prepared by llvm. + * IR-level -- does not contain machine code + * * - function names are unique within a module. **/ std::unique_ptr llvm_module_; diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 7d53a313..df517969 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -98,7 +98,7 @@ namespace xo { llvm::DataLayout dl #endif ) - : kal_jit_{std::move(kal_jit)}, + : kal_jit_{std::move(kal_jit)} #ifdef NOT_USING jit_es_(std::move(jit_es)), jit_data_layout_(std::move(dl)), @@ -114,9 +114,6 @@ namespace xo { jit_our_dynamic_lib_(this->jit_es_->createBareJITDylib("")), /*was MainJD*/ #endif - llvm_cx_{std::make_unique()}, - llvm_ir_builder_{std::make_unique>(*llvm_cx_)}, - llvm_module_{std::make_unique("xojit", *llvm_cx_)} { #ifdef NOT_USING jit_our_dynamic_lib_.addGenerator @@ -129,6 +126,16 @@ namespace xo { } #endif + this->recreate_llvm_ir_pipeline(); + } + + void + Jit::recreate_llvm_ir_pipeline() + { + llvm_cx_ = std::make_unique(); + llvm_ir_builder_ = std::make_unique>(*llvm_cx_); + llvm_module_ = std::make_unique("xojit", *llvm_cx_); + llvm_module_->setDataLayout(kal_jit_->getDataLayout()); if (!llvm_cx_.get()) { @@ -162,7 +169,7 @@ namespace xo { llvm_pass_builder.registerModuleAnalyses(*llvm_mamgr_); llvm_pass_builder.registerFunctionAnalyses(*llvm_famgr_); llvm_pass_builder.crossRegisterProxies(*llvm_lamgr_, *llvm_famgr_, *llvm_cgamgr_, *llvm_mamgr_); - } + } /*recreate_llvm_ir_pipeline*/ const std::string & Jit::target_triple() const { @@ -465,6 +472,21 @@ namespace xo { return nullptr; } /*codegen*/ + void + Jit::machgen_current_module() + { + static llvm::ExitOnError llvm_exit_on_err; + + auto tracker = kal_jit_->getMainJITDylib().createResourceTracker(); + + auto ts_module = llvm::orc::ThreadSafeModule(std::move(llvm_module_), + std::move(llvm_cx_)); + + llvm_exit_on_err(kal_jit_->addModule(std::move(ts_module), tracker)); + + this->recreate_llvm_ir_pipeline(); + } + llvm::orc::ExecutorAddr Jit::lookup_symbol(const std::string & sym) { From 595a3a2d5b3ff3b645a547b8fe473e00ca2a1e59 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 17:14:43 -0400 Subject: [PATCH 1059/2524] xo-pyjit: + Jit.machgen_current_module --- src/pyjit/pyjit.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index c69558a1..69defaec 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -52,6 +52,9 @@ namespace xo { * RC 14jun2024 - I think this is true, modulo use of llvm resource trackers. */ py::return_value_policy::reference_internal) + .def("machgen_current_module", &Jit::machgen_current_module, + py::doc("Make current module available for execution via the jit.\n" + "Adds all functions generated since last call to this method.")) .def("lookup_dbl_dbl_fn", [](Jit & jit, const std::string & symbol) { auto llvm_addr = jit.lookup_symbol(symbol); From e22841148c9849296a4ab68646775ded2e781919 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 17:14:59 -0400 Subject: [PATCH 1060/2524] xo-pyjit: README: spelled out working example. --- README.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1f46c9a5..c739cd31 100644 --- a/README.md +++ b/README.md @@ -52,13 +52,66 @@ Assumes `xo-pyjit` installed to `~/local2/lib`, i.e. built with `PREFIX=~/local2`. ``` PYTHONPATH=~/local2/lib:$PYTHONPATH python ->>> from xo_pyexpression import * >>> from xo_pyjit import * ->>> x=make_constant(3.14159) +>>> from xo_pyexpression import * +``` + +create a jit from within python +``` >>> jit=Jit.make() ->>> code=jit.codegen(x) ->>> x.print -double 3.141600e+00 +>>> jit.dump_execution_sesion() +JITDylib "
" (ES: 0x0000000000446ee0, State = Open) +Link order: [ ("
", MatchAllSymbols) ] +Symbol table: +``` + +build an AST from within python +``` +>>> x=make_var('x') # "x" a variable (context not yet known) +>>> f1=make_sin_pm() # "sin()" +>>> c1=make_apply(f1,x) # "sin(x)" +>>> f2=make_cos_pm() # "cos()" +>>> c2=make_apply(f2,c1) # "cos(sin(x))" +>>> lm=make_lambda('foo', ['x'], c2) # "def foo(x): cos(sin(x))" +>>> lm + :argv "[ :argv \"[]\">]">> +``` + +generate llvm IR for our AST +``` +>>> code=jit.codegen(lm) +>>> print(code.print()) +define double @foo(double %x) { +entry: + %calltmp = call double @sin(double %x) + %calltmp1 = call double @cos(double %calltmp) + ret double %calltmp1 +} +``` + +generate machine code for our AST, lookup compiled function so we can invoke it directly +``` +>>> jit.machgen_current_module() +>>> jit.dump_execution_session() +JITDylib "
" (ES: 0x0000000000446ee0, State = Open) +Link order: [ ("
", MatchAllSymbols) ] +Symbol table: + "foo": [Callable] Never-Searched (Materializer 0x646fe0, xojit) +>>> fn=jit.lookup_dbl_dbl_fn('foo') + +>>> jit.dump_execution_session() +JITDylib "
" (ES: 0x0000000000446ee0, State = Open) +Link order: [ ("
", MatchAllSymbols) ] +Symbol table: + "cos": 0x7ffff7926670 [Data] Ready + "foo": 0x7fffee2b6000 [Callable] Ready + "sin": 0x7ffff7925e50 [Data] Ready +``` + +invoke just-compiled code! +``` +>>> fn(22) +0.999960827417674 ``` ## Development From 537e178e09e5ff5f8a43a751942d85aaaf693e6a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 17:22:33 -0400 Subject: [PATCH 1061/2524] xo-jit: add transform passes (from kal 4) --- src/jit/Jit.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index df517969..1f660348 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -159,10 +159,11 @@ namespace xo { this->llvm_si_->registerCallbacks(*llvm_pic_, llvm_mamgr_.get()); - // TODO: llvm_fpmgr_->addPass(InstCombinePass()) etc. - // TODO: llvm_fpmgr_->addPass(ReassociatePass()) etc. - // TODO: llvm_fpmgr_->addPass(GVNPasss()) etc. - // TODO: llvm_fpmgr_->addPass(SimplifyCFGPass()) etc. + /** transform passes **/ + this->llvm_fpmgr_->addPass(llvm::InstCombinePass()); + this->llvm_fpmgr_->addPass(llvm::ReassociatePass()); + this->llvm_fpmgr_->addPass(llvm::GVNPass()); + this->llvm_fpmgr_->addPass(llvm::SimplifyCFGPass()); /** tracking for analysis passes that share info? **/ llvm::PassBuilder llvm_pass_builder; From f3af5d27bf51c3617b7a79d066cb5749643edbcb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 01:01:49 -0400 Subject: [PATCH 1062/2524] xo-jit: move IR improvement pipeline to dedicated class --- include/xo/jit/IrPipeline.hpp | 75 +++++++++++++++++++++++++++++++++++ include/xo/jit/Jit.hpp | 18 +-------- src/jit/CMakeLists.txt | 1 + src/jit/IrPipeline.cpp | 45 +++++++++++++++++++++ src/jit/Jit.cpp | 27 ++----------- 5 files changed, 126 insertions(+), 40 deletions(-) create mode 100644 include/xo/jit/IrPipeline.hpp create mode 100644 src/jit/IrPipeline.cpp diff --git a/include/xo/jit/IrPipeline.hpp b/include/xo/jit/IrPipeline.hpp new file mode 100644 index 00000000..8719fbe2 --- /dev/null +++ b/include/xo/jit/IrPipeline.hpp @@ -0,0 +1,75 @@ +/** @file IrPipeline.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" + +/* stuff from kaleidoscope.cpp */ +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/PassManager.h" +#include "llvm/IR/Type.h" +#include "llvm/IR/Verifier.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/StandardInstrumentations.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Target/TargetMachine.h" +#include "llvm/Transforms/InstCombine/InstCombine.h" +#include "llvm/Transforms/Scalar.h" +#include "llvm/Transforms/Scalar/GVN.h" +#include "llvm/Transforms/Scalar/Reassociate.h" +#include "llvm/Transforms/Scalar/SimplifyCFG.h" + +//#include + +namespace xo { + namespace jit { + /** @class IrPipeline + * @brief represent an LLVM IR pipeline + * + * Represents analysis/transformation short of generating + * machine-code. For now pipeline stages are hardwired; + * adapted from the LLVM Kaleidoscope example project. + * + * Conversely, pipeline *starts* with code already that has + * already been expressed in LLVM IR + **/ + class IrPipeline : public ref::Refcount { + public: + explicit IrPipeline(llvm::LLVMContext & llvm_cx); + + void run_pipeline(llvm::Function & fn); + + private: + // ----- transforms (also adapted from kaleidescope.cpp) ------ + + /** manages all the passes+analaysis (?) **/ + std::unique_ptr llvm_fpmgr_; + /** loop analysis (?) **/ + std::unique_ptr llvm_lamgr_; + /** function-level analysis (?) **/ + std::unique_ptr llvm_famgr_; + /** cgscc (?) analysis **/ + std::unique_ptr llvm_cgamgr_; + /** module analsyis (?) **/ + std::unique_ptr llvm_mamgr_; + /** pass instrumentation **/ + std::unique_ptr llvm_pic_; + /** standard instrumentation **/ + std::unique_ptr llvm_si_; + }; /*IrPipeline*/ + } /*namespace jit*/ +} /*namespace xo*/ + + +/** end IrPipeline.hpp **/ diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 14b368d3..f5eb65a4 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -8,6 +8,7 @@ //#include #include "xo/refcnt/Refcounted.hpp" +#include "IrPipeline.hpp" #include "xo/expression/Expression.hpp" #include "xo/expression/ConstantInterface.hpp" #include "xo/expression/PrimitiveInterface.hpp" @@ -150,6 +151,7 @@ namespace xo { * kal_jit_.addModule() * Note that this makes the module itself unavailable to us **/ + xo::ref::rp ir_pipeline_; /** owns + manages core "global" llvm data, * including type- and constant- unique-ing tables. @@ -176,22 +178,6 @@ namespace xo { **/ std::map nested_env_; - // ----- transforms (also adapted from kaleidescope.cpp) ------ - - /** manages all the passes+analaysis (?) **/ - std::unique_ptr llvm_fpmgr_; - /** loop analysis (?) **/ - std::unique_ptr llvm_lamgr_; - /** function-level analysis (?) **/ - std::unique_ptr llvm_famgr_; - /** cgscc (?) analysis **/ - std::unique_ptr llvm_cgamgr_; - /** module analsyis (?) **/ - std::unique_ptr llvm_mamgr_; - /** pass instrumentation **/ - std::unique_ptr llvm_pic_; - /** standard instrumentation **/ - std::unique_ptr llvm_si_; }; /*Jit*/ inline std::ostream & diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index abd3c903..bbf5aa99 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -2,6 +2,7 @@ set(SELF_LIB xo_jit) set(SELF_SRCS + IrPipeline.cpp Jit.cpp ) diff --git a/src/jit/IrPipeline.cpp b/src/jit/IrPipeline.cpp new file mode 100644 index 00000000..200d3a52 --- /dev/null +++ b/src/jit/IrPipeline.cpp @@ -0,0 +1,45 @@ +/* @file IrPipeline.cpp */ + +#include "IrPipeline.hpp" + +namespace xo { + namespace jit { + IrPipeline::IrPipeline(llvm::LLVMContext & llvm_cx) + { + using std::make_unique; + + this->llvm_fpmgr_ = make_unique(); + this->llvm_lamgr_ = std::make_unique(); + this->llvm_famgr_ = std::make_unique(); + this->llvm_cgamgr_ = std::make_unique(); + this->llvm_mamgr_ = std::make_unique(); + this->llvm_pic_ = std::make_unique(); + this->llvm_si_ = std::make_unique(llvm_cx, + /*DebugLogging*/ true); + + this->llvm_si_->registerCallbacks(*llvm_pic_, llvm_mamgr_.get()); + + /** transform passes **/ + this->llvm_fpmgr_->addPass(llvm::InstCombinePass()); + this->llvm_fpmgr_->addPass(llvm::ReassociatePass()); + this->llvm_fpmgr_->addPass(llvm::GVNPass()); + this->llvm_fpmgr_->addPass(llvm::SimplifyCFGPass()); + + /** tracking for analysis passes that share info? **/ + llvm::PassBuilder llvm_pass_builder; + llvm_pass_builder.registerModuleAnalyses(*llvm_mamgr_); + llvm_pass_builder.registerFunctionAnalyses(*llvm_famgr_); + llvm_pass_builder.crossRegisterProxies(*llvm_lamgr_, *llvm_famgr_, *llvm_cgamgr_, *llvm_mamgr_); + } /*ctor*/ + + void + IrPipeline::run_pipeline(llvm::Function & fn) + { + llvm_fpmgr_->run(fn, *llvm_famgr_); + } /*run_pipeline*/ + } /*namespace jit*/ +} /*namespace xo*/ + + + +/* end IrPipeline.cpp */ diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 1f660348..67752b7b 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -148,28 +148,7 @@ namespace xo { throw std::runtime_error("Jit::ctor: expected non-empty llvm module"); } - this->llvm_fpmgr_ = std::make_unique(); - this->llvm_lamgr_ = std::make_unique(); - this->llvm_famgr_ = std::make_unique(); - this->llvm_cgamgr_ = std::make_unique(); - this->llvm_mamgr_ = std::make_unique(); - this->llvm_pic_ = std::make_unique(); - this->llvm_si_ = std::make_unique(*llvm_cx_, - /*DebugLogging*/ true); - - this->llvm_si_->registerCallbacks(*llvm_pic_, llvm_mamgr_.get()); - - /** transform passes **/ - this->llvm_fpmgr_->addPass(llvm::InstCombinePass()); - this->llvm_fpmgr_->addPass(llvm::ReassociatePass()); - this->llvm_fpmgr_->addPass(llvm::GVNPass()); - this->llvm_fpmgr_->addPass(llvm::SimplifyCFGPass()); - - /** tracking for analysis passes that share info? **/ - llvm::PassBuilder llvm_pass_builder; - llvm_pass_builder.registerModuleAnalyses(*llvm_mamgr_); - llvm_pass_builder.registerFunctionAnalyses(*llvm_famgr_); - llvm_pass_builder.crossRegisterProxies(*llvm_lamgr_, *llvm_famgr_, *llvm_cgamgr_, *llvm_mamgr_); + ir_pipeline_ = new IrPipeline(*llvm_cx_); } /*recreate_llvm_ir_pipeline*/ const std::string & @@ -420,8 +399,8 @@ namespace xo { /* validate! always validate! */ llvm::verifyFunction(*fn); - /* optimize! does this generate code? */ - llvm_fpmgr_->run(*fn, *llvm_famgr_); + /* optimize! improves IR */ + ir_pipeline_->run_pipeline(*fn); // llvm_fpmgr_->run(*fn, *llvm_famgr_); return fn; } From a927d44e0e757190ea1baa903c7ab91cd72300f8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 11:00:37 -0400 Subject: [PATCH 1063/2524] xo-jit: + LlvmContext to keepalive native LLVMContext --- include/xo/jit/IrPipeline.hpp | 6 ++++- include/xo/jit/Jit.hpp | 5 +++-- include/xo/jit/LlvmContext.hpp | 40 ++++++++++++++++++++++++++++++++++ src/jit/CMakeLists.txt | 1 + src/jit/IrPipeline.cpp | 7 ++++-- src/jit/Jit.cpp | 32 +++++++++++++++++---------- src/jit/LlvmContext.cpp | 20 +++++++++++++++++ 7 files changed, 94 insertions(+), 17 deletions(-) create mode 100644 include/xo/jit/LlvmContext.hpp create mode 100644 src/jit/LlvmContext.cpp diff --git a/include/xo/jit/IrPipeline.hpp b/include/xo/jit/IrPipeline.hpp index 8719fbe2..7efd5b4f 100644 --- a/include/xo/jit/IrPipeline.hpp +++ b/include/xo/jit/IrPipeline.hpp @@ -6,6 +6,7 @@ #pragma once #include "xo/refcnt/Refcounted.hpp" +#include "LlvmContext.hpp" /* stuff from kaleidoscope.cpp */ #include "llvm/ADT/APFloat.h" @@ -46,13 +47,16 @@ namespace xo { **/ class IrPipeline : public ref::Refcount { public: - explicit IrPipeline(llvm::LLVMContext & llvm_cx); + explicit IrPipeline(ref::rp llvm_cx); void run_pipeline(llvm::Function & fn); private: // ----- transforms (also adapted from kaleidescope.cpp) ------ + /** keepalive for contained llvm::LLVMContext **/ + ref::rp llvm_cx_; + /** manages all the passes+analaysis (?) **/ std::unique_ptr llvm_fpmgr_; /** loop analysis (?) **/ diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index f5eb65a4..0cfb423d 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -9,6 +9,7 @@ #include "xo/refcnt/Refcounted.hpp" #include "IrPipeline.hpp" +#include "LlvmContext.hpp" #include "xo/expression/Expression.hpp" #include "xo/expression/ConstantInterface.hpp" #include "xo/expression/PrimitiveInterface.hpp" @@ -31,7 +32,6 @@ #include "llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h" #include "llvm/ExecutionEngine/SectionMemoryManager.h" #include "llvm/IR/DataLayout.h" -#include "llvm/IR/LLVMContext.h" #include #endif @@ -159,7 +159,8 @@ namespace xo { * Not threadsafe, but ok to have multiple threads, * each with its own LLVMContext **/ - std::unique_ptr llvm_cx_; + ref::rp llvm_cx_; + //std::unique_ptr llvm_cx_; /** builder for intermediate-representation objects **/ std::unique_ptr> llvm_ir_builder_; /** a module (1:1 with library) being prepared by llvm. diff --git a/include/xo/jit/LlvmContext.hpp b/include/xo/jit/LlvmContext.hpp new file mode 100644 index 00000000..38f1492a --- /dev/null +++ b/include/xo/jit/LlvmContext.hpp @@ -0,0 +1,40 @@ +/** @file LlvmContext.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include "llvm/IR/LLVMContext.h" +//#include + +namespace xo { + namespace jit { + /** @class LlvmContext + * @brief Keepalive for a llvm::LLVMContext instance. + * + * For example IrPipeline holds an rp + * to help ensure validity of embedded llvm::LLVMContext reference + **/ + class LlvmContext : public ref::Refcount { + public: + static xo::ref::rp make(); + + llvm::LLVMContext & llvm_cx_ref() { return *llvm_cx_; } + std::unique_ptr & llvm_cx() { return llvm_cx_; } + + private: + LlvmContext(); + + private: + /** Llvm context. Ties together fragments of code generation + * for AST subtrees that go into the same module. + **/ + std::unique_ptr llvm_cx_; + }; /*LlvmContext*/ + } /*namespace jit*/ +} /*namespace xo*/ + + +/** end LlvmContext.hpp **/ diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index bbf5aa99..018ecb0e 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -2,6 +2,7 @@ set(SELF_LIB xo_jit) set(SELF_SRCS + LlvmContext.cpp IrPipeline.cpp Jit.cpp ) diff --git a/src/jit/IrPipeline.cpp b/src/jit/IrPipeline.cpp index 200d3a52..d08d9639 100644 --- a/src/jit/IrPipeline.cpp +++ b/src/jit/IrPipeline.cpp @@ -4,17 +4,20 @@ namespace xo { namespace jit { - IrPipeline::IrPipeline(llvm::LLVMContext & llvm_cx) + IrPipeline::IrPipeline(ref::rp llvm_cx) { using std::make_unique; + this->llvm_cx_ = std::move(llvm_cx); + this->llvm_fpmgr_ = make_unique(); this->llvm_lamgr_ = std::make_unique(); this->llvm_famgr_ = std::make_unique(); this->llvm_cgamgr_ = std::make_unique(); this->llvm_mamgr_ = std::make_unique(); this->llvm_pic_ = std::make_unique(); - this->llvm_si_ = std::make_unique(llvm_cx, + /* reference kept alive by @ref llvm_cx_ */ + this->llvm_si_ = std::make_unique(llvm_cx_->llvm_cx_ref(), /*DebugLogging*/ true); this->llvm_si_->registerCallbacks(*llvm_pic_, llvm_mamgr_.get()); diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 67752b7b..0accdd65 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -132,9 +132,10 @@ namespace xo { void Jit::recreate_llvm_ir_pipeline() { - llvm_cx_ = std::make_unique(); - llvm_ir_builder_ = std::make_unique>(*llvm_cx_); - llvm_module_ = std::make_unique("xojit", *llvm_cx_); + //llvm_cx_ = std::make_unique(); + llvm_cx_ = LlvmContext::make(); + llvm_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); + llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); llvm_module_->setDataLayout(kal_jit_->getDataLayout()); @@ -148,7 +149,7 @@ namespace xo { throw std::runtime_error("Jit::ctor: expected non-empty llvm module"); } - ir_pipeline_ = new IrPipeline(*llvm_cx_); + ir_pipeline_ = new IrPipeline(llvm_cx_); } /*recreate_llvm_ir_pipeline*/ const std::string & @@ -176,10 +177,10 @@ namespace xo { TypeDescr td = expr->value_td(); if (td->is_native()) { - return llvm::ConstantFP::get(*llvm_cx_, + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), llvm::APFloat(*(expr->value_tp().recover_native()))); } else if (td->is_native()) { - return llvm::ConstantFP::get(*llvm_cx_, + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), llvm::APFloat(*(expr->value_tp().recover_native()))); } @@ -230,7 +231,7 @@ namespace xo { log && log(xtag("i_arg", i), xtag("arg_td", arg_td->short_name())); if (arg_td->is_native()) { - llvm_argtype_v.push_back(llvm::Type::getDoubleTy(*llvm_cx_)); + llvm_argtype_v.push_back(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref())); // TODO: extend with other native types here... } else { @@ -252,7 +253,7 @@ namespace xo { llvm::Type * llvm_retval = nullptr; if (retval_td->is_native()) { - llvm_retval = llvm::Type::getDoubleTy(*llvm_cx_); + llvm_retval = llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()); } else { cerr << "Jit::codegen_primitive: error: primitive f returning T where double expected" << xtag("f", expr->name()) @@ -363,9 +364,9 @@ namespace xo { // PLACEHOLDER // just handle double arguments + return type for now - std::vector double_v(1, llvm::Type::getDoubleTy(*llvm_cx_)); + std::vector double_v(1, llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref())); - auto * llvm_fn_type = llvm::FunctionType::get(llvm::Type::getDoubleTy(*llvm_cx_), + auto * llvm_fn_type = llvm::FunctionType::get(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), double_v, false /*!varargs*/); @@ -381,7 +382,7 @@ namespace xo { /* generate function body */ - auto block = llvm::BasicBlock::Create(*llvm_cx_, "entry", fn); + auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", fn); llvm_ir_builder_->SetInsertPoint(block); @@ -459,8 +460,15 @@ namespace xo { auto tracker = kal_jit_->getMainJITDylib().createResourceTracker(); + /* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create + * + * Note that @ref ir_pipeline_ holds reference, which is invalidated here + */ auto ts_module = llvm::orc::ThreadSafeModule(std::move(llvm_module_), - std::move(llvm_cx_)); + std::move(llvm_cx_->llvm_cx())); + + /* note does not discard llvm_cx_->llvm_cx(), it's already been moved */ + this->llvm_cx_ = nullptr; llvm_exit_on_err(kal_jit_->addModule(std::move(ts_module), tracker)); diff --git a/src/jit/LlvmContext.cpp b/src/jit/LlvmContext.cpp new file mode 100644 index 00000000..19349aca --- /dev/null +++ b/src/jit/LlvmContext.cpp @@ -0,0 +1,20 @@ +/* @file LlvmContext.cpp */ + +#include "LlvmContext.hpp" + +namespace xo { + namespace jit { + xo::ref::rp + LlvmContext::make() { + return new LlvmContext(); + } + + LlvmContext::LlvmContext() + : llvm_cx_{std::make_unique()} + {} + + } /*namespace jit*/ +} /*namespace xo*/ + + +/* end LlvmContext.cpp */ From 7c3226ee64e1f850687700b714a227ca0db74117 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 11:21:42 -0400 Subject: [PATCH 1064/2524] xo-jit: refactoring -- var names in KaleidoscopeJit --- include/xo/jit/Jit.hpp | 5 -- include/xo/jit/KaleidoscopeJit.hpp | 89 ++++++++++++++++-------------- src/jit/Jit.cpp | 15 ----- 3 files changed, 48 insertions(+), 61 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 0cfb423d..879818c8 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -115,7 +115,6 @@ namespace xo { Jit( std::unique_ptr kal_jit #ifdef NOT_USING - std::unique_ptr es, llvm::orc::JITTargetMachineBuilder jtmb, llvm::DataLayout dl #endif @@ -133,10 +132,6 @@ namespace xo { std::unique_ptr kal_jit_; #ifdef NOT_USING - std::unique_ptr jit_es_; - llvm::DataLayout jit_data_layout_; - llvm::orc::MangleAndInterner jit_mangle_; - llvm::orc::RTDyldObjectLinkingLayer jit_object_layer_; llvm::orc::IRCompileLayer jit_compile_layer_; /** reference here. looks like storage owned by .jit_es **/ llvm::orc::JITDylib & jit_our_dynamic_lib_; diff --git a/include/xo/jit/KaleidoscopeJit.hpp b/include/xo/jit/KaleidoscopeJit.hpp index 89f1a724..5359bbb0 100644 --- a/include/xo/jit/KaleidoscopeJit.hpp +++ b/include/xo/jit/KaleidoscopeJit.hpp @@ -10,8 +10,7 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H -#define LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H +#pragma once #include "llvm/ADT/StringRef.h" #include "llvm/ExecutionEngine/JITSymbol.h" @@ -50,42 +49,47 @@ namespace xo { using SelfExecutorProcessControl = llvm::orc::SelfExecutorProcessControl; private: - /* execution session - represents a currenlty-running jit program */ - std::unique_ptr ES; + /** execution session - represents a currently-running jit program **/ + std::unique_ptr xsession_; - DataLayout DL; - MangleAndInterner Mangle; + /** (?) needed for name mangling (?) **/ + DataLayout data_layout_; + /** symbol mangling and unique-ifying */ + MangleAndInterner mangler_; - RTDyldObjectLinkingLayer ObjectLayer; - IRCompileLayer CompileLayer; + /** in-process linking layer + * (? specialized for jit in running process ?) + **/ + RTDyldObjectLinkingLayer object_layer_; + IRCompileLayer compile_layer_; JITDylib &MainJD; public: - KaleidoscopeJIT(std::unique_ptr ES, - JITTargetMachineBuilder JTMB, - DataLayout DL) - : ES(std::move(ES)), - DL(std::move(DL)), - Mangle(*this->ES, this->DL), - ObjectLayer(*this->ES, - []() { return std::make_unique(); }), - CompileLayer(*this->ES, ObjectLayer, - std::make_unique(std::move(JTMB))), - MainJD(this->ES->createBareJITDylib("
")) + KaleidoscopeJIT(std::unique_ptr xsession, + JITTargetMachineBuilder jtmb, + DataLayout data_layout_) + : xsession_{std::move(xsession)}, + data_layout_(std::move(data_layout_)), + mangler_(*this->xsession_, this->data_layout_), + object_layer_(*this->xsession_, + []() { return std::make_unique(); }), + compile_layer_(*this->xsession_, object_layer_, + std::make_unique(std::move(jtmb))), + MainJD(this->xsession_->createBareJITDylib("
")) { MainJD.addGenerator( cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess( - DL.getGlobalPrefix()))); - if (JTMB.getTargetTriple().isOSBinFormatCOFF()) { - ObjectLayer.setOverrideObjectFlagsWithResponsibilityFlags(true); - ObjectLayer.setAutoClaimResponsibilityForObjectSymbols(true); + data_layout_.getGlobalPrefix()))); + if (jtmb.getTargetTriple().isOSBinFormatCOFF()) { + object_layer_.setOverrideObjectFlagsWithResponsibilityFlags(true); + object_layer_.setAutoClaimResponsibilityForObjectSymbols(true); } } ~KaleidoscopeJIT() { - if (auto Err = ES->endSession()) - ES->reportError(std::move(Err)); + if (auto Err = this->xsession_->endSession()) + this->xsession_->reportError(std::move(Err)); } static llvm::Expected> Create() { @@ -93,40 +97,43 @@ namespace xo { if (!EPC) return EPC.takeError(); - auto ES = std::make_unique(std::move(*EPC)); + auto xsession = std::make_unique(std::move(*EPC)); - JITTargetMachineBuilder JTMB( - ES->getExecutorProcessControl().getTargetTriple()); + JITTargetMachineBuilder jtmb + (xsession->getExecutorProcessControl().getTargetTriple()); - auto DL = JTMB.getDefaultDataLayoutForTarget(); - if (!DL) - return DL.takeError(); + auto data_layout = jtmb.getDefaultDataLayoutForTarget(); + if (!data_layout) + return data_layout.takeError(); - return std::make_unique(std::move(ES), std::move(JTMB), - std::move(*DL)); + return std::make_unique(std::move(xsession), + std::move(jtmb), + std::move(*data_layout)); } - const DataLayout &getDataLayout() const { return DL; } + const DataLayout & getDataLayout() const { return data_layout_; } JITDylib &getMainJITDylib() { return MainJD; } - llvm::Error addModule(ThreadSafeModule TSM, ResourceTrackerSP RT = nullptr) { + llvm::Error + addModule(ThreadSafeModule ts_module, + ResourceTrackerSP RT = nullptr) { if (!RT) RT = MainJD.getDefaultResourceTracker(); - return CompileLayer.add(RT, std::move(TSM)); + + return compile_layer_.add(RT, std::move(ts_module)); } - llvm::Expected lookup(StringRef Name) { - return ES->lookup({&MainJD}, Mangle(Name.str())); + llvm::Expected lookup(StringRef name) { + return this->xsession_->lookup({&MainJD}, + this->mangler_(name.str())); } /* dump */ void dump_execution_session() { - ES->dump(llvm::errs()); + this->xsession_->dump(llvm::errs()); } }; } // end namespace jit } // end namespace xo - -#endif // LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 0accdd65..43cb7e2c 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -73,11 +73,6 @@ namespace xo { std::unique_ptr kal_jit = llvm_exit_on_err(KaleidoscopeJIT::Create()); return std::unique_ptr(new Jit(std::move(kal_jit) -#ifdef NOT_USING - std::move(es), - std::move(jtmb), - std::move(*dl) -#endif )); } /*make*/ @@ -93,18 +88,12 @@ namespace xo { Jit::Jit( std::unique_ptr kal_jit #ifdef NOT_USING - std::unique_ptr jit_es, llvm::orc::JITTargetMachineBuilder jtmb, llvm::DataLayout dl #endif ) : kal_jit_{std::move(kal_jit)} #ifdef NOT_USING - jit_es_(std::move(jit_es)), - jit_data_layout_(std::move(dl)), - jit_mangle_(*this->jit_es_, this->jit_data_layout_), - jit_object_layer_(*this->jit_es_, - []() { return std::make_unique(); }), jit_compile_layer_(*this->jit_es_, jit_object_layer_, std::make_unique(std::move(jtmb))), @@ -119,10 +108,6 @@ namespace xo { jit_our_dynamic_lib_.addGenerator (cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess (jit_data_layout_.getGlobalPrefix()))); - - if(jtmb.getTargetTriple().isOSBinFormatCOFF()) { - jit_object_layer_.setOverrideObjectFlagsWithResponsibilityFlags(true); - jit_object_layer_.setAutoClaimResponsibilityForObjectSymbols(true); } #endif From c16686fd4cce950458d5f69131094542076f1f73 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 11:27:03 -0400 Subject: [PATCH 1065/2524] xo-jit: tidy: drop never-compiled obsolete code --- include/xo/jit/Jit.hpp | 30 +--------------- include/xo/jit/KaleidoscopeJit.hpp | 2 +- src/jit/Jit.cpp | 55 +++--------------------------- 3 files changed, 6 insertions(+), 81 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 879818c8..9ef11939 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -18,22 +18,6 @@ #include "xo/expression/Variable.hpp" #include "KaleidoscopeJit.hpp" -#ifdef NOT_USING -/* stuff from KaleidoscopeJIT.hpp */ -#include "llvm/ADT/StringRef.h" -#include "llvm/ExecutionEngine/JITSymbol.h" -#include "llvm/ExecutionEngine/Orc/CompileUtils.h" -#include "llvm/ExecutionEngine/Orc/Core.h" -#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" -#include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h" -#include "llvm/ExecutionEngine/Orc/IRCompileLayer.h" -#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h" -#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" -#include "llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h" -#include "llvm/ExecutionEngine/SectionMemoryManager.h" -#include "llvm/IR/DataLayout.h" -#include -#endif /* stuff from kaleidoscope.cpp */ #include "llvm/ADT/APFloat.h" @@ -112,13 +96,7 @@ namespace xo { virtual std::string display_string() const; private: - Jit( - std::unique_ptr kal_jit -#ifdef NOT_USING - llvm::orc::JITTargetMachineBuilder jtmb, - llvm::DataLayout dl -#endif - ); + Jit(std::unique_ptr kal_jit); /* iniitialize native builder (i.e. for platform we're running on) */ static void init_once(); @@ -131,12 +109,6 @@ namespace xo { std::unique_ptr kal_jit_; -#ifdef NOT_USING - llvm::orc::IRCompileLayer jit_compile_layer_; - /** reference here. looks like storage owned by .jit_es **/ - llvm::orc::JITDylib & jit_our_dynamic_lib_; -#endif - // ----- this part adapted from kaleidoscope.cpp ----- /** everything bleow represents a pipeline diff --git a/include/xo/jit/KaleidoscopeJit.hpp b/include/xo/jit/KaleidoscopeJit.hpp index 5359bbb0..97f65358 100644 --- a/include/xo/jit/KaleidoscopeJit.hpp +++ b/include/xo/jit/KaleidoscopeJit.hpp @@ -111,7 +111,7 @@ namespace xo { std::move(*data_layout)); } - const DataLayout & getDataLayout() const { return data_layout_; } + const DataLayout & data_layout() const { return data_layout_; } JITDylib &getMainJITDylib() { return MainJD; } diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 43cb7e2c..dafbd07e 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -43,31 +43,6 @@ namespace xo { { Jit::init_once(); -#ifdef NOT_USING - /* 'executor process control' */ - auto epc = llvm::orc::SelfExecutorProcessControl::Create(); - if (!epc) { - return epc.takeError(); - //throw std::runtime_error("Jit::make: failed to establish executor process control"); - } - - /* 'execution session' */ - auto es = std::make_unique(std::move(*epc)); - - /* 'jit target machine builder' */ - llvm::orc::JITTargetMachineBuilder jtmb(es - ->getExecutorProcessControl() - .getTargetTriple()); - - auto dl = jtmb.getDefaultDataLayoutForTarget(); - if (!dl) { - return dl.takeError(); - //throw std::runtime_error("Jit::make: failed to establish data layout object" - // " for target machine"); - } -#endif - - static llvm::ExitOnError llvm_exit_on_err; std::unique_ptr kal_jit = llvm_exit_on_err(KaleidoscopeJIT::Create()); @@ -85,32 +60,9 @@ namespace xo { return jit.release(); } /*make*/ - Jit::Jit( - std::unique_ptr kal_jit -#ifdef NOT_USING - llvm::orc::JITTargetMachineBuilder jtmb, - llvm::DataLayout dl -#endif - ) + Jit::Jit(std::unique_ptr kal_jit) : kal_jit_{std::move(kal_jit)} -#ifdef NOT_USING - jit_compile_layer_(*this->jit_es_, - jit_object_layer_, - std::make_unique(std::move(jtmb))), - /* note: string passed to createBareJITDyLib must be unique - * (within running process address space?) - */ - jit_our_dynamic_lib_(this->jit_es_->createBareJITDylib("")), /*was MainJD*/ -#endif - { -#ifdef NOT_USING - jit_our_dynamic_lib_.addGenerator - (cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess - (jit_data_layout_.getGlobalPrefix()))); - } -#endif - this->recreate_llvm_ir_pipeline(); } @@ -120,9 +72,9 @@ namespace xo { //llvm_cx_ = std::make_unique(); llvm_cx_ = LlvmContext::make(); llvm_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); - llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); - llvm_module_->setDataLayout(kal_jit_->getDataLayout()); + llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); + llvm_module_->setDataLayout(kal_jit_->data_layout()); if (!llvm_cx_.get()) { throw std::runtime_error("Jit::ctor: expected non-empty llvm context"); @@ -139,6 +91,7 @@ namespace xo { const std::string & Jit::target_triple() const { + // although this getter is defined, seems to be empty in practice return llvm_module_->getTargetTriple(); } From a23e0f56c6e3be57487d6ee633b0bc97e4d902a6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 11:34:27 -0400 Subject: [PATCH 1066/2524] xo-jit: fix Jit::target_triple() --- include/xo/jit/KaleidoscopeJit.hpp | 4 ++++ src/jit/Jit.cpp | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/include/xo/jit/KaleidoscopeJit.hpp b/include/xo/jit/KaleidoscopeJit.hpp index 97f65358..f3d2ebcd 100644 --- a/include/xo/jit/KaleidoscopeJit.hpp +++ b/include/xo/jit/KaleidoscopeJit.hpp @@ -111,6 +111,10 @@ namespace xo { std::move(*data_layout)); } + const std::string & target_triple() const { + return xsession_->getTargetTriple().getTriple(); + } + const DataLayout & data_layout() const { return data_layout_; } JITDylib &getMainJITDylib() { return MainJD; } diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index dafbd07e..f1e00828 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -89,10 +89,15 @@ namespace xo { ir_pipeline_ = new IrPipeline(llvm_cx_); } /*recreate_llvm_ir_pipeline*/ + /** identifies target host/architecture for machine code. + * e.g. "x86_64-unknown-linux-gnu" + **/ const std::string & Jit::target_triple() const { // although this getter is defined, seems to be empty in practice - return llvm_module_->getTargetTriple(); + //return llvm_module_->getTargetTriple(); + + return kal_jit_->target_triple(); } std::vector From 932e7cd966cc60a7aee91bd6d4c2015f55d28dc8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 11:49:23 -0400 Subject: [PATCH 1067/2524] xo-jit: refactor: Jit -> MachPipeline --- example/ex1/ex1.cpp | 6 +- include/xo/jit/{Jit.hpp => MachPipeline.hpp} | 18 ++--- src/jit/CMakeLists.txt | 2 +- src/jit/{Jit.cpp => MachPipeline.cpp} | 76 ++++++++++---------- 4 files changed, 51 insertions(+), 51 deletions(-) rename include/xo/jit/{Jit.hpp => MachPipeline.hpp} (92%) rename src/jit/{Jit.cpp => MachPipeline.cpp} (84%) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 77ee265c..238ba4a3 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -1,6 +1,6 @@ /** @file ex1.cpp **/ -#include "xo/jit/Jit.hpp" +#include "xo/jit/MachPipeline.hpp" #include "xo/expression/Constant.hpp" #include "xo/expression/Primitive.hpp" #include "xo/expression/Apply.hpp" @@ -35,7 +35,7 @@ int main() { using xo::scope; - using xo::jit::Jit; + using xo::jit::MachPipeline; using xo::ast::make_constant; using xo::ast::make_primitive; using xo::ast::make_apply; @@ -54,7 +54,7 @@ main() { llvm::InitializeNativeTargetAsmParser(); //auto jit = llvm_exit_on_err(Jit::make_aux()); - auto jit = Jit::make(); + auto jit = MachPipeline::make(); //static_assert(std::is_function_v); diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/MachPipeline.hpp similarity index 92% rename from include/xo/jit/Jit.hpp rename to include/xo/jit/MachPipeline.hpp index 9ef11939..71df826f 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -1,4 +1,4 @@ -/** @file Jit.hpp +/** @file MachPipeline.hpp * * Author: Roland Conybeare **/ @@ -45,20 +45,20 @@ namespace xo { namespace jit { - /** @class Jit + /** @class MachPipeline * @brief just-in-time compiler for EGAD * * TODO: make module name a parameter? **/ - class Jit : public ref::Refcount { + class MachPipeline : public ref::Refcount { public: using Expression = xo::ast::Expression; //using ConstantInterface = xo::ast::ConstantInterface; public: /* tracking KaleidoscopeJIT::Create() here.. */ - static llvm::Expected> make_aux(); - static xo::ref::rp make(); + static llvm::Expected> make_aux(); + static xo::ref::rp make(); // ----- module access ----- @@ -96,7 +96,7 @@ namespace xo { virtual std::string display_string() const; private: - Jit(std::unique_ptr kal_jit); + MachPipeline(std::unique_ptr kal_jit); /* iniitialize native builder (i.e. for platform we're running on) */ static void init_once(); @@ -146,10 +146,10 @@ namespace xo { **/ std::map nested_env_; - }; /*Jit*/ + }; /*MachPipeline*/ inline std::ostream & - operator<<(std::ostream & os, const Jit & x) { + operator<<(std::ostream & os, const MachPipeline & x) { x.display(os); return os; } @@ -157,4 +157,4 @@ namespace xo { } /*namespace xo*/ -/** end Jit.hpp **/ +/** end MachPipeline.hpp **/ diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index 018ecb0e..88b9d2da 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -4,7 +4,7 @@ set(SELF_LIB xo_jit) set(SELF_SRCS LlvmContext.cpp IrPipeline.cpp - Jit.cpp + MachPipeline.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) diff --git a/src/jit/Jit.cpp b/src/jit/MachPipeline.cpp similarity index 84% rename from src/jit/Jit.cpp rename to src/jit/MachPipeline.cpp index f1e00828..4acbbf3c 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/MachPipeline.cpp @@ -1,6 +1,6 @@ -/* @file Jit.cpp */ +/* @file MachPipeline.cpp */ -#include "Jit.hpp" +#include "MachPipeline.hpp" namespace xo { using xo::ast::exprtype; @@ -16,7 +16,7 @@ namespace xo { namespace jit { void - Jit::init_once() { + MachPipeline::init_once() { static bool s_init_once = false; if (!s_init_once) { @@ -38,36 +38,36 @@ namespace xo { * + 'jit_copmile_layer' * + 'jit_our_dynamic_lib' */ - llvm::Expected> - Jit::make_aux() + llvm::Expected> + MachPipeline::make_aux() { - Jit::init_once(); + MachPipeline::init_once(); static llvm::ExitOnError llvm_exit_on_err; std::unique_ptr kal_jit = llvm_exit_on_err(KaleidoscopeJIT::Create()); - return std::unique_ptr(new Jit(std::move(kal_jit) + return std::unique_ptr(new MachPipeline(std::move(kal_jit) )); } /*make*/ - xo::ref::rp - Jit::make() { + xo::ref::rp + MachPipeline::make() { static llvm::ExitOnError llvm_exit_on_err; - std::unique_ptr jit = llvm_exit_on_err(make_aux()); + std::unique_ptr jit = llvm_exit_on_err(make_aux()); return jit.release(); } /*make*/ - Jit::Jit(std::unique_ptr kal_jit) + MachPipeline::MachPipeline(std::unique_ptr kal_jit) : kal_jit_{std::move(kal_jit)} { this->recreate_llvm_ir_pipeline(); } void - Jit::recreate_llvm_ir_pipeline() + MachPipeline::recreate_llvm_ir_pipeline() { //llvm_cx_ = std::make_unique(); llvm_cx_ = LlvmContext::make(); @@ -77,13 +77,13 @@ namespace xo { llvm_module_->setDataLayout(kal_jit_->data_layout()); if (!llvm_cx_.get()) { - throw std::runtime_error("Jit::ctor: expected non-empty llvm context"); + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); } if (!llvm_ir_builder_.get()) { - throw std::runtime_error("Jit::ctor: expected non-empty llvm IR builder"); + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm IR builder"); } if (!llvm_module_.get()) { - throw std::runtime_error("Jit::ctor: expected non-empty llvm module"); + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm module"); } ir_pipeline_ = new IrPipeline(llvm_cx_); @@ -93,7 +93,7 @@ namespace xo { * e.g. "x86_64-unknown-linux-gnu" **/ const std::string & - Jit::target_triple() const { + MachPipeline::target_triple() const { // although this getter is defined, seems to be empty in practice //return llvm_module_->getTargetTriple(); @@ -101,7 +101,7 @@ namespace xo { } std::vector - Jit::get_function_name_v() { + MachPipeline::get_function_name_v() { std::vector retval; for (const auto & fn_name : *llvm_module_) retval.push_back(fn_name.getName().str()); @@ -110,12 +110,12 @@ namespace xo { } /*get_function_names*/ void - Jit::dump_execution_session() { + MachPipeline::dump_execution_session() { kal_jit_->dump_execution_session(); } llvm::Value * - Jit::codegen_constant(ref::brw expr) + MachPipeline::codegen_constant(ref::brw expr) { TypeDescr td = expr->value_td(); @@ -131,7 +131,7 @@ namespace xo { } llvm::Function * - Jit::codegen_primitive(ref::brw expr) + MachPipeline::codegen_primitive(ref::brw expr) { constexpr bool c_debug_flag = true; using xo::scope; @@ -178,7 +178,7 @@ namespace xo { // TODO: extend with other native types here... } else { - cerr << "Jit::codegen_primitive: error: primitive f with arg i of type T where double expected" + cerr << "MachPipeline::codegen_primitive: error: primitive f with arg i of type T where double expected" << xtag("f", expr->name()) << xtag("i", i) << xtag("T", arg_td->short_name()) @@ -198,7 +198,7 @@ namespace xo { if (retval_td->is_native()) { llvm_retval = llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()); } else { - cerr << "Jit::codegen_primitive: error: primitive f returning T where double expected" + cerr << "MachPipeline::codegen_primitive: error: primitive f returning T where double expected" << xtag("f", expr->name()) << xtag("T", retval_td->short_name()) << endl; @@ -234,7 +234,7 @@ namespace xo { } /*codegen_primitive*/ llvm::Value * - Jit::codegen_apply(ref::brw apply) + MachPipeline::codegen_apply(ref::brw apply) { using std::cerr; using std::endl; @@ -251,13 +251,13 @@ namespace xo { auto * fn = this->codegen_primitive(pm); #ifdef NOT_USING_DEBUG - cerr << "Jit::codegen_apply: fn:" << endl; + cerr << "MachPipeline::codegen_apply: fn:" << endl; fn->print(llvm::errs()); cerr << endl; #endif if (fn->arg_size() != apply->argv().size()) { - cerr << "Jit::codegen_apply: error: callee f expecting n1 args where n2 supplied" + cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" << xtag("f", pm->name()) << xtag("n1", pm->n_arg()) << xtag("n2", apply->argv().size()) @@ -272,7 +272,7 @@ namespace xo { auto * arg = this->codegen(arg_expr); #ifdef NOT_USING_DEBUG - cerr << "Jit::codegen_apply: arg:" << endl; + cerr << "MachPipeline::codegen_apply: arg:" << endl; arg->print(llvm::errs()); cerr << endl; #endif @@ -282,13 +282,13 @@ namespace xo { return llvm_ir_builder_->CreateCall(fn, args, "calltmp"); } else { - cerr << "Jit::codegen_apply: error: only allowing call to known primitives at present" << endl; + cerr << "MachPipeline::codegen_apply: error: only allowing call to known primitives at present" << endl; return nullptr; } } /*codegen_apply*/ llvm::Function * - Jit::codegen_lambda(ref::brw lambda) + MachPipeline::codegen_lambda(ref::brw lambda) { /* reminder! this is the *expression*, not the *closure* */ @@ -356,12 +356,12 @@ namespace xo { } /*codegen_lambda*/ llvm::Value * - Jit::codegen_variable(ref::brw var) + MachPipeline::codegen_variable(ref::brw var) { auto ix = nested_env_.find(var->name()); if (ix == nested_env_.end()) { - cerr << "Jit::codegen_variable: no binding for variable x" + cerr << "MachPipeline::codegen_variable: no binding for variable x" << xtag("x", var->name()) << endl; } @@ -370,7 +370,7 @@ namespace xo { } /*codegen_variable*/ llvm::Value * - Jit::codegen(ref::brw expr) + MachPipeline::codegen(ref::brw expr) { switch(expr->extype()) { case exprtype::constant: @@ -389,7 +389,7 @@ namespace xo { break; } - cerr << "Jit::codegen: error: no handler for expression of type T" + cerr << "MachPipeline::codegen: error: no handler for expression of type T" << xtag("T", expr->extype()) << endl; @@ -397,7 +397,7 @@ namespace xo { } /*codegen*/ void - Jit::machgen_current_module() + MachPipeline::machgen_current_module() { static llvm::ExitOnError llvm_exit_on_err; @@ -419,7 +419,7 @@ namespace xo { } llvm::orc::ExecutorAddr - Jit::lookup_symbol(const std::string & sym) + MachPipeline::lookup_symbol(const std::string & sym) { static llvm::ExitOnError llvm_exit_on_err; @@ -433,15 +433,15 @@ namespace xo { } /*lookup_symbol*/ void - Jit::display(std::ostream & os) const { - os << ""; + MachPipeline::display(std::ostream & os) const { + os << ""; } std::string - Jit::display_string() const { + MachPipeline::display_string() const { return tostr(*this); } } /*namespace jit*/ } /*namespace xo*/ -/* end Jit.cpp */ +/* end MachPipeline.cpp */ From a4af2a79d01c5847dea9cbd291747fc51e6f1de1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 11:49:50 -0400 Subject: [PATCH 1068/2524] xo-pyjit: refactor: Jit -> MachPipeline --- README.md | 14 +++++++------- src/pyjit/pyjit.cpp | 23 ++++++++++++----------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index c739cd31..e9f08673 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,8 @@ PYTHONPATH=~/local2/lib:$PYTHONPATH python create a jit from within python ``` ->>> jit=Jit.make() ->>> jit.dump_execution_sesion() +>>> mp=MachPipeline.make() +>>> mp.dump_execution_sesion() JITDylib "
" (ES: 0x0000000000446ee0, State = Open) Link order: [ ("
", MatchAllSymbols) ] Symbol table: @@ -79,7 +79,7 @@ build an AST from within python generate llvm IR for our AST ``` ->>> code=jit.codegen(lm) +>>> code=mp.codegen(lm) >>> print(code.print()) define double @foo(double %x) { entry: @@ -91,15 +91,15 @@ entry: generate machine code for our AST, lookup compiled function so we can invoke it directly ``` ->>> jit.machgen_current_module() ->>> jit.dump_execution_session() +>>> mp.machgen_current_module() +>>> mp.dump_execution_session() JITDylib "
" (ES: 0x0000000000446ee0, State = Open) Link order: [ ("
", MatchAllSymbols) ] Symbol table: "foo": [Callable] Never-Searched (Materializer 0x646fe0, xojit) ->>> fn=jit.lookup_dbl_dbl_fn('foo') +>>> fn=mp.lookup_dbl_dbl_fn('foo') ->>> jit.dump_execution_session() +>>> mp.dump_execution_session() JITDylib "
" (ES: 0x0000000000446ee0, State = Open) Link order: [ ("
", MatchAllSymbols) ] Symbol table: diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 69defaec..624f8882 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -2,7 +2,7 @@ #include "pyjit.hpp" #include "xo/pyexpression/pyexpression.hpp" -#include "xo/jit/Jit.hpp" +#include "xo/jit/MachPipeline.hpp" #include "xo/pyutil/pyutil.hpp" #include @@ -29,19 +29,20 @@ namespace xo { m.doc() = "pybind11 plugin for xo-jit"; - py::class_>(m, "Jit") - .def_static("make", &Jit::make, - py::doc("create Jit instance. Not threadsafe," - " but does not share resources with any other Jit instance")) + py::class_>(m, "MachPipeline") + .def_static("make", &MachPipeline::make, + py::doc("Create machine pipeline for in-process code generation" + " and execution. Not threadsafe.\n" + "Does not share resources with any other instance")) - .def_property_readonly("target_triple", &Jit::target_triple, + .def_property_readonly("target_triple", &MachPipeline::target_triple, py::doc("string describing target host for code generation")) - .def("get_function_name_v", &Jit::get_function_name_v, + .def("get_function_name_v", &MachPipeline::get_function_name_v, py::doc("get vector of function names defined in jit module")) - .def("dump_execution_session", &Jit::dump_execution_session, + .def("dump_execution_session", &MachPipeline::dump_execution_session, py::doc("write to console with state of all jit-owned dynamic libraries")) .def("codegen", - [](Jit & jit, const rp & expr) { + [](MachPipeline & jit, const rp & expr) { return jit.codegen(expr.borrow()); }, py::arg("x"), @@ -52,11 +53,11 @@ namespace xo { * RC 14jun2024 - I think this is true, modulo use of llvm resource trackers. */ py::return_value_policy::reference_internal) - .def("machgen_current_module", &Jit::machgen_current_module, + .def("machgen_current_module", &MachPipeline::machgen_current_module, py::doc("Make current module available for execution via the jit.\n" "Adds all functions generated since last call to this method.")) .def("lookup_dbl_dbl_fn", - [](Jit & jit, const std::string & symbol) { + [](MachPipeline & jit, const std::string & symbol) { auto llvm_addr = jit.lookup_symbol(symbol); auto fn_addr = llvm_addr.toPtr(); From 6d7de854da4a6e1a2afa23e28b489e36440dbcef Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 12:00:57 -0400 Subject: [PATCH 1069/2524] xo-jit: refactor: KaleidoscopeJIT -> Jit + ancillary renames --- .../xo/jit/{KaleidoscopeJit.hpp => Jit.hpp} | 54 +++++++++---------- include/xo/jit/MachPipeline.hpp | 7 +-- src/jit/MachPipeline.cpp | 4 +- 3 files changed, 31 insertions(+), 34 deletions(-) rename include/xo/jit/{KaleidoscopeJit.hpp => Jit.hpp} (76%) diff --git a/include/xo/jit/KaleidoscopeJit.hpp b/include/xo/jit/Jit.hpp similarity index 76% rename from include/xo/jit/KaleidoscopeJit.hpp rename to include/xo/jit/Jit.hpp index f3d2ebcd..fa188245 100644 --- a/include/xo/jit/KaleidoscopeJit.hpp +++ b/include/xo/jit/Jit.hpp @@ -1,14 +1,6 @@ -//===- KaleidoscopeJIT.h - A simple JIT for Kaleidoscope --------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// Contains a simple JIT definition for use in the kaleidoscope tutorials. -// -//===----------------------------------------------------------------------===// +/** @file Jit.hpp **/ + +/** Adapted from LLVM KaleidoscopeJIT.h **/ #pragma once @@ -30,7 +22,7 @@ namespace xo { namespace jit { - class KaleidoscopeJIT { + class Jit { private: using StringRef = llvm::StringRef; using SectionMemoryManager = llvm::SectionMemoryManager; @@ -61,14 +53,16 @@ namespace xo { * (? specialized for jit in running process ?) **/ RTDyldObjectLinkingLayer object_layer_; + /** compilation layer (sits above linking layer) **/ IRCompileLayer compile_layer_; - JITDylib &MainJD; + /** destination library **/ + JITDylib & dest_dynamic_lib_; //MainJD; public: - KaleidoscopeJIT(std::unique_ptr xsession, - JITTargetMachineBuilder jtmb, - DataLayout data_layout_) + Jit(std::unique_ptr xsession, + JITTargetMachineBuilder jtmb, + DataLayout data_layout_) : xsession_{std::move(xsession)}, data_layout_(std::move(data_layout_)), mangler_(*this->xsession_, this->data_layout_), @@ -76,9 +70,9 @@ namespace xo { []() { return std::make_unique(); }), compile_layer_(*this->xsession_, object_layer_, std::make_unique(std::move(jtmb))), - MainJD(this->xsession_->createBareJITDylib("
")) + dest_dynamic_lib_(this->xsession_->createBareJITDylib("
")) { - MainJD.addGenerator( + dest_dynamic_lib_.addGenerator( cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess( data_layout_.getGlobalPrefix()))); if (jtmb.getTargetTriple().isOSBinFormatCOFF()) { @@ -87,12 +81,12 @@ namespace xo { } } - ~KaleidoscopeJIT() { + ~Jit() { if (auto Err = this->xsession_->endSession()) this->xsession_->reportError(std::move(Err)); } - static llvm::Expected> Create() { + static llvm::Expected> Create() { auto EPC = SelfExecutorProcessControl::Create(); if (!EPC) return EPC.takeError(); @@ -106,9 +100,9 @@ namespace xo { if (!data_layout) return data_layout.takeError(); - return std::make_unique(std::move(xsession), - std::move(jtmb), - std::move(*data_layout)); + return std::make_unique(std::move(xsession), + std::move(jtmb), + std::move(*data_layout)); } const std::string & target_triple() const { @@ -117,19 +111,19 @@ namespace xo { const DataLayout & data_layout() const { return data_layout_; } - JITDylib &getMainJITDylib() { return MainJD; } + JITDylib &getMainJITDylib() { return dest_dynamic_lib_; } llvm::Error addModule(ThreadSafeModule ts_module, ResourceTrackerSP RT = nullptr) { if (!RT) - RT = MainJD.getDefaultResourceTracker(); + RT = dest_dynamic_lib_.getDefaultResourceTracker(); return compile_layer_.add(RT, std::move(ts_module)); } llvm::Expected lookup(StringRef name) { - return this->xsession_->lookup({&MainJD}, + return this->xsession_->lookup({&dest_dynamic_lib_}, this->mangler_(name.str())); } @@ -137,7 +131,9 @@ namespace xo { void dump_execution_session() { this->xsession_->dump(llvm::errs()); } - }; + }; /*Jit*/ - } // end namespace jit -} // end namespace xo + } /*namespace jit&*/ +} /*namespace xo*/ + +/** end Jit.hpp **/ diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 71df826f..834390c3 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -10,6 +10,8 @@ #include "xo/refcnt/Refcounted.hpp" #include "IrPipeline.hpp" #include "LlvmContext.hpp" +#include "Jit.hpp" + #include "xo/expression/Expression.hpp" #include "xo/expression/ConstantInterface.hpp" #include "xo/expression/PrimitiveInterface.hpp" @@ -17,7 +19,6 @@ #include "xo/expression/Lambda.hpp" #include "xo/expression/Variable.hpp" -#include "KaleidoscopeJit.hpp" /* stuff from kaleidoscope.cpp */ #include "llvm/ADT/APFloat.h" @@ -96,7 +97,7 @@ namespace xo { virtual std::string display_string() const; private: - MachPipeline(std::unique_ptr kal_jit); + MachPipeline(std::unique_ptr jit); /* iniitialize native builder (i.e. for platform we're running on) */ static void init_once(); @@ -107,7 +108,7 @@ namespace xo { private: // ----- this part adapted from LLVM 19.0 KaleidoscopeJIT.hpp [wip] ----- - std::unique_ptr kal_jit_; + std::unique_ptr kal_jit_; // ----- this part adapted from kaleidoscope.cpp ----- diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 4acbbf3c..54ab95f8 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -45,7 +45,7 @@ namespace xo { static llvm::ExitOnError llvm_exit_on_err; - std::unique_ptr kal_jit = llvm_exit_on_err(KaleidoscopeJIT::Create()); + std::unique_ptr kal_jit = llvm_exit_on_err(Jit::Create()); return std::unique_ptr(new MachPipeline(std::move(kal_jit) )); @@ -60,7 +60,7 @@ namespace xo { return jit.release(); } /*make*/ - MachPipeline::MachPipeline(std::unique_ptr kal_jit) + MachPipeline::MachPipeline(std::unique_ptr kal_jit) : kal_jit_{std::move(kal_jit)} { this->recreate_llvm_ir_pipeline(); From fb3ccff6170c56b5efbe47fe33b7e022e47258bc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 12:02:26 -0400 Subject: [PATCH 1070/2524] xo-jit: refactor: Jit.getMainJITDyLib -> dest_dynamic_lib_ref() --- include/xo/jit/Jit.hpp | 2 +- src/jit/MachPipeline.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index fa188245..83b62c29 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -111,7 +111,7 @@ namespace xo { const DataLayout & data_layout() const { return data_layout_; } - JITDylib &getMainJITDylib() { return dest_dynamic_lib_; } + JITDylib & dest_dynamic_lib_ref() { return dest_dynamic_lib_; } llvm::Error addModule(ThreadSafeModule ts_module, diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 54ab95f8..235a8afb 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -401,7 +401,7 @@ namespace xo { { static llvm::ExitOnError llvm_exit_on_err; - auto tracker = kal_jit_->getMainJITDylib().createResourceTracker(); + auto tracker = kal_jit_->dest_dynamic_lib_ref().createResourceTracker(); /* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create * From b6ece858568a941cab411f6d9d609528378fb94f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 12:06:28 -0400 Subject: [PATCH 1071/2524] xo-jit: refactor: MachPipeline.kal_jit -> jit --- include/xo/jit/MachPipeline.hpp | 7 +++++-- src/jit/MachPipeline.cpp | 16 ++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 834390c3..4ffc029c 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -108,7 +108,10 @@ namespace xo { private: // ----- this part adapted from LLVM 19.0 KaleidoscopeJIT.hpp [wip] ----- - std::unique_ptr kal_jit_; + /** just-in-time compiler -- construct machine code that can + * be invoked from this running process + **/ + std::unique_ptr jit_; // ----- this part adapted from kaleidoscope.cpp ----- @@ -116,7 +119,7 @@ namespace xo { * that takes expressions, and turns them into llvm IR. * * llvm IR can be added to running JIT by calling - * kal_jit_.addModule() + * jit_->addModule() * Note that this makes the module itself unavailable to us **/ xo::ref::rp ir_pipeline_; diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 235a8afb..f912a394 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -60,8 +60,8 @@ namespace xo { return jit.release(); } /*make*/ - MachPipeline::MachPipeline(std::unique_ptr kal_jit) - : kal_jit_{std::move(kal_jit)} + MachPipeline::MachPipeline(std::unique_ptr jit) + : jit_{std::move(jit)} { this->recreate_llvm_ir_pipeline(); } @@ -74,7 +74,7 @@ namespace xo { llvm_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); - llvm_module_->setDataLayout(kal_jit_->data_layout()); + llvm_module_->setDataLayout(this->jit_->data_layout()); if (!llvm_cx_.get()) { throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); @@ -97,7 +97,7 @@ namespace xo { // although this getter is defined, seems to be empty in practice //return llvm_module_->getTargetTriple(); - return kal_jit_->target_triple(); + return this->jit_->target_triple(); } std::vector @@ -111,7 +111,7 @@ namespace xo { void MachPipeline::dump_execution_session() { - kal_jit_->dump_execution_session(); + this->jit_->dump_execution_session(); } llvm::Value * @@ -401,7 +401,7 @@ namespace xo { { static llvm::ExitOnError llvm_exit_on_err; - auto tracker = kal_jit_->dest_dynamic_lib_ref().createResourceTracker(); + auto tracker = this->jit_->dest_dynamic_lib_ref().createResourceTracker(); /* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create * @@ -413,7 +413,7 @@ namespace xo { /* note does not discard llvm_cx_->llvm_cx(), it's already been moved */ this->llvm_cx_ = nullptr; - llvm_exit_on_err(kal_jit_->addModule(std::move(ts_module), tracker)); + llvm_exit_on_err(this->jit_->addModule(std::move(ts_module), tracker)); this->recreate_llvm_ir_pipeline(); } @@ -424,7 +424,7 @@ namespace xo { static llvm::ExitOnError llvm_exit_on_err; /* llvm_sym: ExecutorSymbolDef */ - auto llvm_sym = llvm_exit_on_err(kal_jit_->lookup(sym)); + auto llvm_sym = llvm_exit_on_err(this->jit_->lookup(sym)); /* llvm_addr: ExecutorAddr */ auto llvm_addr = llvm_sym.getAddress(); From 9bcb86e8bcd6d1a63df96ac31fa0c312518f973f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 12:10:17 -0400 Subject: [PATCH 1072/2524] xo-jit: refactor: Jit.addModule() -> add_llvm_module() --- include/xo/jit/Jit.hpp | 14 +++++++++----- src/jit/MachPipeline.cpp | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 83b62c29..feb78652 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -113,13 +113,17 @@ namespace xo { JITDylib & dest_dynamic_lib_ref() { return dest_dynamic_lib_; } + /** compile module to machine code that's runnable from this process; + * incorporate into @ref dest_dynamic_lib_ + **/ llvm::Error - addModule(ThreadSafeModule ts_module, - ResourceTrackerSP RT = nullptr) { - if (!RT) - RT = dest_dynamic_lib_.getDefaultResourceTracker(); + add_llvm_module(ThreadSafeModule ts_module, + ResourceTrackerSP rtracker = nullptr) { + if (!rtracker) + rtracker = dest_dynamic_lib_.getDefaultResourceTracker(); - return compile_layer_.add(RT, std::move(ts_module)); + return compile_layer_.add(rtracker, + std::move(ts_module)); } llvm::Expected lookup(StringRef name) { diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index f912a394..e02e7316 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -413,7 +413,7 @@ namespace xo { /* note does not discard llvm_cx_->llvm_cx(), it's already been moved */ this->llvm_cx_ = nullptr; - llvm_exit_on_err(this->jit_->addModule(std::move(ts_module), tracker)); + llvm_exit_on_err(this->jit_->add_llvm_module(std::move(ts_module), tracker)); this->recreate_llvm_ir_pipeline(); } From 596ecbdf66c80b4bace694c35e36d9558a85c5a7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 12:14:51 -0400 Subject: [PATCH 1073/2524] xo-jit: refactor: cosmetic --- include/xo/jit/MachPipeline.hpp | 5 ++--- src/jit/MachPipeline.cpp | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 4ffc029c..8deede36 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -131,10 +131,9 @@ namespace xo { * each with its own LLVMContext **/ ref::rp llvm_cx_; - //std::unique_ptr llvm_cx_; /** builder for intermediate-representation objects **/ std::unique_ptr> llvm_ir_builder_; - /** a module (1:1 with library) being prepared by llvm. + /** a module (1:1 with library ?) being prepared by llvm. * IR-level -- does not contain machine code * * - function names are unique within a module. @@ -144,7 +143,7 @@ namespace xo { /** map global names to functions/variables **/ std::map> global_env_; /** map variable names (formal parameters) to - * corresponding llvm interactor + * corresponding llvm IR. * * only supports one level atm (i.e. only top-level functions) **/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index e02e7316..02b27b00 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -45,9 +45,9 @@ namespace xo { static llvm::ExitOnError llvm_exit_on_err; - std::unique_ptr kal_jit = llvm_exit_on_err(Jit::Create()); + std::unique_ptr jit = llvm_exit_on_err(Jit::Create()); - return std::unique_ptr(new MachPipeline(std::move(kal_jit) + return std::unique_ptr(new MachPipeline(std::move(jit) )); } /*make*/ From bfbd097db578119a9aec504bfce3bd18646f185d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 12:16:55 -0400 Subject: [PATCH 1074/2524] xo-jit: minor tidy (comments) --- include/xo/jit/MachPipeline.hpp | 6 ++++-- src/jit/MachPipeline.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 8deede36..07fd6050 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -91,15 +91,17 @@ namespace xo { **/ void machgen_current_module(); + /** lookup symbol in jit-associated output library **/ llvm::orc::ExecutorAddr lookup_symbol(const std::string & x); virtual void display(std::ostream & os) const; virtual std::string display_string() const; private: - MachPipeline(std::unique_ptr jit); + /** construct instance, adopting jit for compilation+execution **/ + explicit MachPipeline(std::unique_ptr jit); - /* iniitialize native builder (i.e. for platform we're running on) */ + /** iniitialize native builder (i.e. for platform we're running on) **/ static void init_once(); /** (re)create pipeline to turn expressions into llvm IR code **/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 02b27b00..68d8a127 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -35,7 +35,7 @@ namespace xo { * can instantiate from python * + 'jit object layer' * (realtime dynamic library object linking layer) - * + 'jit_copmile_layer' + * + 'jit_compile_layer' * + 'jit_our_dynamic_lib' */ llvm::Expected> From f7e4433a1dacbab63a459bc936d42a1e7267d7cb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 11:47:17 -0400 Subject: [PATCH 1075/2524] xo-jit: fix Lambda generation to handle multiple arguments --- src/jit/MachPipeline.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 68d8a127..1619d121 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -155,7 +155,7 @@ namespace xo { /** establish prototype for this function **/ // PLACEHOLDER - // just make prototype for function :: double -> double + // just make prototype for function :: double^n -> double TypeDescr fn_td = expr->value_td(); int n_fn_arg = fn_td->n_fn_arg(); @@ -307,7 +307,8 @@ namespace xo { // PLACEHOLDER // just handle double arguments + return type for now - std::vector double_v(1, llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref())); + std::vector double_v(lambda->n_arg(), + llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref())); auto * llvm_fn_type = llvm::FunctionType::get(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), double_v, From 6783cb7e8cc6823e021ab82b6ff336b87992f125 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 12:25:12 -0400 Subject: [PATCH 1076/2524] xo-expression: use Reflect::require_function() for precision --- include/xo/expression/Lambda.hpp | 3 +++ include/xo/expression/Primitive.hpp | 13 ++++++++----- include/xo/expression/PrimitiveInterface.hpp | 4 +++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index 7f6434f8..7f5ebab4 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -35,6 +35,9 @@ namespace xo { const std::vector & argv() const { return argv_; } const ref::rp & body() const { return body_; } + /** return number of arguments expected by this function **/ + int n_arg() const { return argv_.size(); } + // ----- Expression ----- virtual void display(std::ostream & os) const override; diff --git a/include/xo/expression/Primitive.hpp b/include/xo/expression/Primitive.hpp index afae94a9..f4fc21b7 100644 --- a/include/xo/expression/Primitive.hpp +++ b/include/xo/expression/Primitive.hpp @@ -33,17 +33,20 @@ namespace xo { FunctionPointer fnptr) : PrimitiveInterface(), name_{name}, - value_td_{Reflect::require()}, + value_td_{Reflect::require_function()}, value_{fnptr} - {} + { + if (!value_td_->is_function()) + throw std::runtime_error("Primitive: expected function pointer"); + if (!value_td_->fn_retval()) + throw std::runtime_error("Primitive: expected non-null function return value"); + } FunctionPointer value() const { return value_; } // ----- PrimitiveInterface ----- - virtual std::string const & name() const { return name_; } - /** FIXME for now **/ - virtual int n_arg() const { return 1; } + virtual std::string const & name() const override { return name_; } // ----- ConstantInterface ----- diff --git a/include/xo/expression/PrimitiveInterface.hpp b/include/xo/expression/PrimitiveInterface.hpp index c294a440..3274195f 100644 --- a/include/xo/expression/PrimitiveInterface.hpp +++ b/include/xo/expression/PrimitiveInterface.hpp @@ -22,7 +22,9 @@ namespace xo { } virtual const std::string & name() const = 0; - virtual int n_arg() const = 0; + int n_arg() const { return this->value_td()->n_fn_arg(); } + TypeDescr fn_retval() const { return this->value_td()->fn_retval(); } + TypeDescr fn_arg(uint32_t i) const { return this->value_td()->fn_arg(i); } //virtual TypeDescr value_td() const override = 0; //virtual TaggedPtr value_tp() const override = 0; From 6b0f49970ae59cf8cd42f56c1052f5bee6331b99 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 12:25:46 -0400 Subject: [PATCH 1077/2524] xo-expression: ++ example to build primitive --- example/ex1/ex1.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 19dddc82..5c12a4b1 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -1,14 +1,34 @@ /** @file ex1.cpp **/ #include "xo/expression/Constant.hpp" +#include "xo/expression/Primitive.hpp" #include +#include int main() { using xo::ast::make_constant; + using xo::ast::make_primitive; + using std::cout; + using std::endl; - auto expr = make_constant(7); + { + auto expr = make_constant(7); + } + + { + auto expr = make_primitive("sqrt", &::sqrt); + + auto expr_td = expr->value_td(); + + cout << "expr_td: " << expr_td->short_name() << endl; + cout << "expr_td->is_function(): " << expr_td->is_function() << endl; + cout << "expr_td->fn_retval(): " << expr_td->fn_retval()->short_name() << endl; + cout << "expr_td->n_fn_arg(): " << expr_td->n_fn_arg() << endl; + for (uint32_t i = 0; i < expr_td->n_fn_arg(); ++i) + cout << "expr_td->fn_arg(" << i << "): " << expr_td->fn_arg(i)->short_name() << endl; + cout << "expr_td->fn_is_noexcept(): " << expr_td->fn_is_noexcept() << endl; + } } - /** end ex1.cpp **/ From 012597b112bf2498f63e028d1eaf1044146562d2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 12:26:24 -0400 Subject: [PATCH 1078/2524] xo-expression: + if-expressions --- include/xo/expression/IfExpr.hpp | 67 ++++++++++++++++++++++++++++++ include/xo/expression/exprtype.hpp | 3 ++ src/expression/CMakeLists.txt | 2 +- src/expression/IfExpr.cpp | 20 +++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 include/xo/expression/IfExpr.hpp create mode 100644 src/expression/IfExpr.cpp diff --git a/include/xo/expression/IfExpr.hpp b/include/xo/expression/IfExpr.hpp new file mode 100644 index 00000000..d60ab1d3 --- /dev/null +++ b/include/xo/expression/IfExpr.hpp @@ -0,0 +1,67 @@ +/** @file IfExpr.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "Expression.hpp" +#include +#include +//#include + +namespace xo { + namespace ast { + /** @class IfExpr + * @brief abstract syntax tree for a function definition + * + **/ + class IfExpr : public Expression { + public: + /** @p test test-expression; always execute + * @p when_true then-branch; executes only when test succeeds + * @p when_false else-branch; executes only when test fails + **/ + IfExpr(const ref::rp & test, + const ref::rp & when_true, + const ref::rp & when_false) + : Expression(exprtype::if_expr), + test_{test}, + when_true_{when_true}, + when_false_{when_false} {} + + /** downcast from Expression **/ + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + const ref::rp & test() const { return test_; } + const ref::rp & when_true() const { return when_true_; } + const ref::rp & when_false() const { return when_false_; } + + // ----- Expression ----- + + virtual void display(std::ostream & os) const override; + + private: + /** if: + * (if x y z) + * + * executes x; if true execute y; otherwise execute z + **/ + ref::rp test_; + ref::rp when_true_; + ref::rp when_false_; + }; /*IfExpr*/ + + inline ref::rp + make_if_expr(const ref::rp & test, + const ref::rp & when_true, + const ref::rp & when_false) + { + return new IfExpr(test, when_true, when_false); + } + } /*namespace ast*/ +} /*namespace xo*/ + +/** end IfExpr.hpp **/ diff --git a/include/xo/expression/exprtype.hpp b/include/xo/expression/exprtype.hpp index 91bcfbb5..ee33190e 100644 --- a/include/xo/expression/exprtype.hpp +++ b/include/xo/expression/exprtype.hpp @@ -28,6 +28,8 @@ namespace xo { lambda, /** variable reference **/ variable, + /** if-then-else **/ + if_expr, /** not an expression. comes last, counts entries **/ n_expr @@ -43,6 +45,7 @@ namespace xo { case exprtype::apply: return "apply"; case exprtype::lambda: return "lambda"; case exprtype::variable: return "variable"; + case exprtype::if_expr: return "if_expr"; default: break; } diff --git a/src/expression/CMakeLists.txt b/src/expression/CMakeLists.txt index 0ef351e1..ef5cf6f2 100644 --- a/src/expression/CMakeLists.txt +++ b/src/expression/CMakeLists.txt @@ -6,7 +6,7 @@ set(SELF_SRCS Apply.cpp Lambda.cpp Variable.cpp - #init_reflect.cpp + IfExpr.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) diff --git a/src/expression/IfExpr.cpp b/src/expression/IfExpr.cpp new file mode 100644 index 00000000..a12fbdb7 --- /dev/null +++ b/src/expression/IfExpr.cpp @@ -0,0 +1,20 @@ +/* @file IfExpr.cpp */ + +#include "IfExpr.hpp" +#include "xo/indentlog/print/vector.hpp" + +namespace xo { + namespace ast { + void + IfExpr::display(std::ostream & os) const { + os << ""; + } /*display*/ + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end IfExpr.cpp */ From fe2053a7bec7bb63028380e734d5c96af0680bac Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 12:26:35 -0400 Subject: [PATCH 1079/2524] xo-expresion: doc: + links --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 205ba405..151c0493 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # xo-expression library A library for representing abstract syntax trees for EGAD (a small expression-based language). +See also +- [github/Rconybea/xo-jit](https://github.com/Rconybea/xo-jit) + EGAD code generation via LLVM +- [github/Rconybea/xo-pyexpression](https://github.com/Rconybea/xo-pyexpression) + build EGAD expressions from a python session +- [github/Rconybea/xo-pyjit](https://github.com/Rconybea/xo-pyjit) + compile + run EGAD expressions from a python session ## Getting Started From 32c74511914a77a29729b6bcacfe3cd2a0810cfd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 12:29:15 -0400 Subject: [PATCH 1080/2524] xo-jit: + codegen for if-expressions --- include/xo/jit/MachPipeline.hpp | 5 +- src/jit/MachPipeline.cpp | 82 +++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 07fd6050..ca59dfb3 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -18,7 +18,7 @@ #include "xo/expression/Apply.hpp" #include "xo/expression/Lambda.hpp" #include "xo/expression/Variable.hpp" - +#include "xo/expression/IfExpr.hpp" /* stuff from kaleidoscope.cpp */ #include "llvm/ADT/APFloat.h" @@ -81,6 +81,7 @@ namespace xo { llvm::Value * codegen_apply(ref::brw expr); llvm::Function * codegen_lambda(ref::brw expr); llvm::Value * codegen_variable(ref::brw var); + llvm::Value * codegen_ifexpr(ref::brw ifexpr); llvm::Value * codegen(ref::brw expr); @@ -117,7 +118,7 @@ namespace xo { // ----- this part adapted from kaleidoscope.cpp ----- - /** everything bleow represents a pipeline + /** everything below represents a pipeline * that takes expressions, and turns them into llvm IR. * * llvm IR can be added to running JIT by calling diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 1619d121..18c7245e 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -10,6 +10,7 @@ namespace xo { using xo::ast::Lambda; using xo::ast::Variable; using xo::ast::Apply; + using xo::ast::IfExpr; using xo::reflect::TypeDescr; using std::cerr; using std::endl; @@ -370,6 +371,85 @@ namespace xo { return ix->second; } /*codegen_variable*/ + llvm::Value * + MachPipeline::codegen_ifexpr(ref::brw expr) + { + llvm::Value * test_ir = this->codegen(expr->test()); + + /** need test result in a variable + **/ + llvm::Value * test_with_cmp_ir + = llvm_ir_builder_->CreateFCmpONE(test_ir, + llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(0.0)), + "iftest"); + + llvm::Function * parent_fn = llvm_ir_builder_->GetInsertBlock()->getParent(); + + /* when_true_bb, when_false_bb, merge_bb: + * initially-empty basic-blocks for {when_true, when_false, merged} codegen + */ + + /* when_true branch inserted at (current) end of function */ + llvm::BasicBlock * when_true_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_true", + parent_fn); + llvm::BasicBlock * when_false_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_false"); + + llvm::BasicBlock * merge_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "merge"); + + /* IR to direct control flow to one of {when_true_bb, when_false_bb}, + * depending on result of test_with_cmp_ir + */ + llvm_ir_builder_->CreateCondBr(test_with_cmp_ir, + when_true_bb, + when_false_bb); + + /* populate when_true_bb */ + llvm_ir_builder_->SetInsertPoint(when_true_bb); + + llvm::Value * when_true_ir = this->codegen(expr->when_true()); + + if (!when_true_ir) + return nullptr; + + /* at end of when-true sequence, jump to merge suffix */ + llvm_ir_builder_->CreateBr(merge_bb); + /* note: codegen for expr->when_true() may have altered builder's "current block" */ + when_true_bb = llvm_ir_builder_->GetInsertBlock(); + + /* populate when_false_bb */ + parent_fn->insert(parent_fn->end(), when_false_bb); + llvm_ir_builder_->SetInsertPoint(when_false_bb); + + llvm::Value * when_false_ir = this->codegen(expr->when_false()); + if (!when_false_ir) + return nullptr; + + /* at end of when-false sequence, jump to merge suffix */ + llvm_ir_builder_->CreateBr(merge_bb); + /* note: codegen for expr->when_false() may have altered builder's "current block" */ + when_false_bb = llvm_ir_builder_->GetInsertBlock(); + + /* merged suffix sequence */ + parent_fn->insert(parent_fn->end(), merge_bb); + llvm_ir_builder_->SetInsertPoint(merge_bb); + + llvm::PHINode * phi_node + = llvm_ir_builder_->CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), + 2 /*#of branches being merged (?)*/, + "iftmp"); + phi_node->addIncoming(when_true_ir, when_true_bb); + phi_node->addIncoming(when_false_ir, when_false_bb); + + return phi_node; + } + llvm::Value * MachPipeline::codegen(ref::brw expr) { @@ -384,6 +464,8 @@ namespace xo { return this->codegen_lambda(Lambda::from(expr)); case exprtype::variable: return this->codegen_variable(Variable::from(expr)); + case exprtype::if_expr: + return this->codegen_ifexpr(IfExpr::from(expr)); case exprtype::invalid: case exprtype::n_expr: return nullptr; From 78605a758d20745d6f4fb7464300fcbf598651f1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 13:43:21 -0400 Subject: [PATCH 1081/2524] xo-expression: minor refactor: if_expr -> ifexpr --- include/xo/expression/Expression.hpp | 3 +++ include/xo/expression/IfExpr.hpp | 8 ++++---- include/xo/expression/exprtype.hpp | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/include/xo/expression/Expression.hpp b/include/xo/expression/Expression.hpp index cd23df53..c9c4e0c0 100644 --- a/include/xo/expression/Expression.hpp +++ b/include/xo/expression/Expression.hpp @@ -20,6 +20,9 @@ namespace xo { * - execute it on a VM * - compile using LLVM * see xo-jit/ + * + * Expressions are immutable. This means they can resused + * across jit interactions **/ class Expression : public ref::Refcount { public: diff --git a/include/xo/expression/IfExpr.hpp b/include/xo/expression/IfExpr.hpp index d60ab1d3..592c585c 100644 --- a/include/xo/expression/IfExpr.hpp +++ b/include/xo/expression/IfExpr.hpp @@ -25,7 +25,7 @@ namespace xo { IfExpr(const ref::rp & test, const ref::rp & when_true, const ref::rp & when_false) - : Expression(exprtype::if_expr), + : Expression(exprtype::ifexpr), test_{test}, when_true_{when_true}, when_false_{when_false} {} @@ -55,9 +55,9 @@ namespace xo { }; /*IfExpr*/ inline ref::rp - make_if_expr(const ref::rp & test, - const ref::rp & when_true, - const ref::rp & when_false) + make_ifexpr(const ref::rp & test, + const ref::rp & when_true, + const ref::rp & when_false) { return new IfExpr(test, when_true, when_false); } diff --git a/include/xo/expression/exprtype.hpp b/include/xo/expression/exprtype.hpp index ee33190e..d8e66a61 100644 --- a/include/xo/expression/exprtype.hpp +++ b/include/xo/expression/exprtype.hpp @@ -29,7 +29,7 @@ namespace xo { /** variable reference **/ variable, /** if-then-else **/ - if_expr, + ifexpr, /** not an expression. comes last, counts entries **/ n_expr @@ -45,7 +45,7 @@ namespace xo { case exprtype::apply: return "apply"; case exprtype::lambda: return "lambda"; case exprtype::variable: return "variable"; - case exprtype::if_expr: return "if_expr"; + case exprtype::ifexpr: return "if_expr"; default: break; } From a36b6f5c75dc0945c1f26cf4c482db5d01d839c9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 13:43:41 -0400 Subject: [PATCH 1082/2524] xo-expression: Apply::make() takes vector of argument expressions --- include/xo/expression/Apply.hpp | 48 ++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/include/xo/expression/Apply.hpp b/include/xo/expression/Apply.hpp index 1f2b9a8f..24f4849d 100644 --- a/include/xo/expression/Apply.hpp +++ b/include/xo/expression/Apply.hpp @@ -20,10 +20,11 @@ namespace xo { **/ class Apply : public Expression { public: - Apply(const ref::rp & fn, - const std::vector> & argv) - : Expression(exprtype::apply), fn_{fn}, argv_(argv) - {} + static ref::rp make(const ref::rp & fn, + const std::vector> & argv) + { + return new Apply(fn, argv); + } /** downcast from Expression **/ static ref::brw from(ref::brw x) { @@ -35,6 +36,12 @@ namespace xo { virtual void display(std::ostream & os) const; + private: + Apply(const ref::rp & fn, + const std::vector> & argv) + : Expression(exprtype::apply), fn_{fn}, argv_(argv) + {} + private: /** function to invoke **/ ref::rp fn_; @@ -42,13 +49,36 @@ namespace xo { std::vector> argv_; }; /*Apply*/ + namespace detail { + /** Use: + ** std::vector> + **/ + + template + struct apply_push_args; + + template <> + struct apply_push_args<> { + static void p9ush_all(std::vector> * /*p_argv*/) {} + }; + + template + struct apply_push_args { + static void push_all(std::vector> * p_argv, + const ref::rp & x, Rest... rest) + { + p_argv->push_back(x); + apply_push_args::push_all(p_argv, rest...); + }; + }; + } + + /* reminder: initializer-lists are compile-time only */ inline ref::rp make_apply(const ref::rp & fn, - const ref::rp & arg1) { - std::vector> argv; - argv.push_back(arg1); - - return new Apply(fn, argv); + const std::initializer_list> args) { + std::vector> argv(args); + return Apply::make(fn, argv); } /*make_apply*/ } /*namespace ast*/ From f71cb12831b86bc4074103a8156d1e573443204d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 13:45:13 -0400 Subject: [PATCH 1083/2524] xo-expression: disable unused detail::apply_push_args --- include/xo/expression/Apply.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/xo/expression/Apply.hpp b/include/xo/expression/Apply.hpp index 24f4849d..acb27a5f 100644 --- a/include/xo/expression/Apply.hpp +++ b/include/xo/expression/Apply.hpp @@ -49,6 +49,7 @@ namespace xo { std::vector> argv_; }; /*Apply*/ +#ifdef NOT_USING namespace detail { /** Use: ** std::vector> @@ -59,7 +60,7 @@ namespace xo { template <> struct apply_push_args<> { - static void p9ush_all(std::vector> * /*p_argv*/) {} + static void push_all(std::vector> * /*p_argv*/) {} }; template @@ -72,6 +73,7 @@ namespace xo { }; }; } +#endif /* reminder: initializer-lists are compile-time only */ inline ref::rp From 8d122425a07ce368f835611204162dbd52049bf6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 13:46:37 -0400 Subject: [PATCH 1084/2524] xo-jit: track new make_apply interface --- example/ex1/ex1.cpp | 6 +++--- src/jit/MachPipeline.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 238ba4a3..01d6073b 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -104,7 +104,7 @@ main() { auto fn = make_primitive("sqrt", &sqrt); auto arg = make_constant(2.0); - auto call = make_apply(fn, arg); + auto call = make_apply(fn, {arg}); log && log(xtag("expr", call)); @@ -129,8 +129,8 @@ main() { auto cos = make_primitive("cos", ::cos); auto x_var = make_var("x"); - auto call1 = make_apply(cos, x_var); /* (cos x) */ - auto call2 = make_apply(sin, call1); /* (sin (cos x)) */ + auto call1 = make_apply(cos, {x_var}); /* (cos x) */ + auto call2 = make_apply(sin, {call1}); /* (sin (cos x)) */ /* (define (lm_1 x) (sin (cos x))) */ auto lambda = make_lambda("lm_1", diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 18c7245e..7081e181 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -464,7 +464,7 @@ namespace xo { return this->codegen_lambda(Lambda::from(expr)); case exprtype::variable: return this->codegen_variable(Variable::from(expr)); - case exprtype::if_expr: + case exprtype::ifexpr: return this->codegen_ifexpr(IfExpr::from(expr)); case exprtype::invalid: case exprtype::n_expr: From 0759cc0c518219c6c853aba7ec383bb363af7503 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 13:47:04 -0400 Subject: [PATCH 1085/2524] xo-jit: bugfix: shortcircuit on unknown variable --- src/jit/MachPipeline.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 7081e181..c41a920e 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -366,6 +366,7 @@ namespace xo { cerr << "MachPipeline::codegen_variable: no binding for variable x" << xtag("x", var->name()) << endl; + return nullptr; } return ix->second; From 3298fdf277ca2050597289bf77481ebed1aecacb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 13:48:03 -0400 Subject: [PATCH 1086/2524] xo-jit: bugfix + debug for codegen_lambda() --- src/jit/MachPipeline.cpp | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index c41a920e..969cfd04 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -291,6 +291,12 @@ namespace xo { llvm::Function * MachPipeline::codegen_lambda(ref::brw lambda) { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + /* reminder! this is the *expression*, not the *closure* */ global_env_[lambda->name()] = lambda.get(); @@ -300,6 +306,10 @@ namespace xo { if (fn) { /** function with this name already defined?? **/ + cerr << "MachPipeline::codegen_lambda: function f already defined" + << xtag("f", lambda->name()) + << endl; + return nullptr; } @@ -321,9 +331,15 @@ namespace xo { lambda->name(), llvm_module_.get()); /* also capture argument names */ - int i = 0; - for (auto & arg : fn->args()) - arg.setName(lambda->argv().at(i)); + { + int i = 0; + for (auto & arg : fn->args()) { + log && log("llvm format param names", xtag("i", i), xtag("param", lambda->argv().at(i))); + + arg.setName(lambda->argv().at(i)); + ++i; + } + } /* generate function body */ @@ -333,8 +349,15 @@ namespace xo { /* formal parameters need to appear in named_value_map_ */ nested_env_.clear(); - for (auto & arg : fn->args()) - nested_env_[std::string(arg.getName())] = &arg; + { + int i = 0; + for (auto & arg : fn->args()) { + log && log("nested environment", xtag("i", i), xtag("param", std::string(arg.getName()))); + + nested_env_[std::string(arg.getName())] = &arg; + ++i; + } + } llvm::Value * retval = this->codegen(lambda->body()); From ecd315c0f2c93cfd699e881931e134cd697cc316 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 16:53:07 -0400 Subject: [PATCH 1087/2524] xo-pyuitl: ++ .projectile in .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 13c0afb7..bc625ff8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# emacs project config +.projectile # clangd working space (see emacs+lsp) .cache # typical cmake build directory (source-tree-nephew) From cd687d2ac40f92370693e6d1e8698e637eec8a30 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 16:53:23 -0400 Subject: [PATCH 1088/2524] xo-pyutil: + pycaller assistant for registering function pointers --- include/xo/pyutil/pycaller.hpp | 132 +++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 include/xo/pyutil/pycaller.hpp diff --git a/include/xo/pyutil/pycaller.hpp b/include/xo/pyutil/pycaller.hpp new file mode 100644 index 00000000..85bf6652 --- /dev/null +++ b/include/xo/pyutil/pycaller.hpp @@ -0,0 +1,132 @@ +/** @file pycaller.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include + +//#include + +namespace xo { + namespace pyutil { + struct pycaller_base { + virtual ~pycaller_base() = default; +g + static pybind11::module & declare_once(pybind11::module & m) { + static bool s_once = false; + if (!s_once) { + s_once = true; + pybind11::class_(m, "pycaller_base"); + } + return m; + } + }; + + /** Invoke function pointer of type Retval(*)(Args...), + * with arguments converted from py::object, and return type converted back to py::object. + * + * Each distinct combination of {Retval,Args...} needs to be established at compile time + * (since we need PyCall to be instantiated for particular types) + * + * Use when we don't know function pointer until *runtime*, + * for example getting function pointer from just-compiled code using xo-pyjit + **/ + template + struct pycaller; + + template + struct pycaller : public pycaller_base { + using self_type = pycaller; + using function_type = Retval (*)(); + + pycaller(function_type addr) : fptr_{reinterpret_cast(addr)} {} + + static pybind11::module & declare_once(pybind11::module & m) { + static bool s_once = false; + if (!s_once) { + s_once = true; + pycaller_base::declare_once(m); + pybind11::class_(m, "pycaller0") + .def("__call__", + [](self_type & self) + { + return pybind11::cast((*self.fptr_)()); + }); + } + return m; + } + + pybind11::object operator()() { return pybind11::cast((*fptr_)()); } + + private: + function_type fptr_; + }; + + template + struct pycaller : public pycaller_base { + using self_type = pycaller; + using function_type = Retval (*)(Arg1); + + pycaller(function_type addr) : fptr_{reinterpret_cast(addr)} {} + + static pybind11::module & declare_once(pybind11::module & m) { + static bool s_once = false; + if (!s_once) { + s_once = true; + pycaller_base::declare_once(m); + pybind11::class_(m, "pycaller1") + .def("__call__", + [](self_type & self, Arg1 arg1) + { + return pybind11::cast((*self.fptr_)(arg1)); + }) + ; + } + return m; + } + + pybind11::object operator()(pybind11::object arg1) { + return pybind11::cast((*fptr_)(pybind11::cast(arg1))); + } + + private: + function_type fptr_; + }; + + template + struct pycaller : public pycaller_base { + using self_type = pycaller; + using function_type = Retval (*)(Arg1, Arg2); + + pycaller(function_type addr) : fptr_{reinterpret_cast(addr)} {} + + static pybind11::module & declare_once(pybind11::module & m) { + static bool s_once = false; + if (!s_once) { + s_once = true; + pycaller_base::declare_once(m); + pybind11::class_(m, "pycaller2") + .def("__call__", + [](self_type & self, Arg1 arg1, Arg2 arg2) + { + return pybind11::cast((*self.fptr_)(arg1, arg2)); + }) + ; + } + return m; + } + + pybind11::object operator()(pybind11::object arg1, pybind11::object arg2) { + return pybind11::cast((*fptr_)(pybind11::cast(arg1), + pybind11::cast(arg2))); + } + + private: + function_type fptr_; + }; + } /*namespace pyutil*/ +} + +/** end pycaller.hpp **/ From bc601c472c484a6e8a1bd7ede2a49948c7b93f28 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 16:54:24 -0400 Subject: [PATCH 1089/2524] xo-pyutil: remove stray character --- include/xo/pyutil/pycaller.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/pyutil/pycaller.hpp b/include/xo/pyutil/pycaller.hpp index 85bf6652..8368c060 100644 --- a/include/xo/pyutil/pycaller.hpp +++ b/include/xo/pyutil/pycaller.hpp @@ -13,7 +13,7 @@ namespace xo { namespace pyutil { struct pycaller_base { virtual ~pycaller_base() = default; -g + static pybind11::module & declare_once(pybind11::module & m) { static bool s_once = false; if (!s_once) { From a4396576c84ae0563fc7494f477cef91f40dd977 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 16:55:24 -0400 Subject: [PATCH 1090/2524] xo-pyexpression: + if-expressions --- src/pyexpression/pyexpression.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index ff808c0c..a74c7076 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -10,6 +10,7 @@ #include "xo/expression/Constant.hpp" #include "xo/expression/Variable.hpp" #include "xo/expression/Lambda.hpp" +#include "xo/expression/IfExpr.hpp" #include "xo/pyutil/pyutil.hpp" #include #include @@ -29,6 +30,8 @@ namespace xo { using xo::ast::make_var; using xo::ast::Lambda; using xo::ast::make_lambda; + using xo::ast::IfExpr; + using xo::ast::make_ifexpr; using xo::reflect::TaggedPtr; using xo::ref::rp; namespace py = pybind11; @@ -46,6 +49,7 @@ namespace xo { .value("apply", exprtype::apply) .value("lambda", exprtype::lambda) .value("variable", exprtype::variable) + .value("ifexpr", exprtype::ifexpr) ; py::class_>(m, "IfExpr") + .def_property_readonly("test", &IfExpr::test, py::doc("test expression")) + .def_property_readonly("when_true", &IfExpr::when_true, py::doc("execute this expression when (and only when) test succeeds")) + .def_property_readonly("when_false", &IfExpr::when_false, py::doc("execute this expression when (and only when) test fails")) + ; + + m.def("make_ifexpr", &make_ifexpr); } /*pyexpresion*/ } /*namespace ast*/ } /*namespace xo*/ From 8c44daf87d7064a46e989dde4323d412cf7d2c63 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 16:56:03 -0400 Subject: [PATCH 1091/2524] xo-pyexpression: + make_pow_pm() --- src/pyexpression/pyexpression.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index a74c7076..1b945f87 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -110,11 +110,17 @@ namespace xo { py::doc("create primitive representing the ::sin() function")); m.def("make_cos_pm", []() { return make_primitive("cos", ::cos); }, py::doc("create primitive representing the ::cos() function")); + m.def("make_pow_pm", []() { return make_primitive("pow", ::pow); }, + py::doc("create primitive representing the ::pow() function")); py::class_, PrimitiveInterface, rp>>(m, "Primitive_double_double") ; + py::class_, + PrimitiveInterface, + rp>>(m, "Primitive_double_double_double") + ; // ----- Apply ----- From ac2c0a7f263d2e3d675ae6b9f7114a5f38eaf2a6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 16:56:30 -0400 Subject: [PATCH 1092/2524] xo-pyexpression: use Apply::make() instead of make_apply() --- src/pyexpression/pyexpression.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index 1b945f87..6ba4ec54 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -129,7 +129,7 @@ namespace xo { .def_property_readonly("argv", &Apply::argv, py::doc("expressions (in position order) for function arguments")) ; - m.def("make_apply", &make_apply); + m.def("make_apply", &Apply::make); // ----- Variables ----- From abd33daec0d22144918aa6575ebf5ebe6832cdd2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 16:56:42 -0400 Subject: [PATCH 1093/2524] xo-pyexpression: + lambda getters .type_str .n_arg --- src/pyexpression/pyexpression.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index 6ba4ec54..bc58917d 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -143,8 +143,10 @@ namespace xo { py::class_>(m, "Lambda") .def_property_readonly("name", &Lambda::name, py::doc("lambda name (maybe automatically generated?)")) + .def_property_readonly("type_str", &Lambda::type_str, py::doc("string specifying lambda type. e.g. \"double(double,double)\"")) .def_property_readonly("argv", &Lambda::argv, py::doc("lambda formal parameters")) .def_property_readonly("body", &Lambda::body, py::doc("lambda body expression")) + .def_property_readonly("n_arg", &Lambda::n_arg, py::doc("number of format parameters to this lambda function")) ; m.def("make_lambda", &make_lambda); From b18de1b0ce51288254a75615f98a50b6a3b1dfd4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 17:00:16 -0400 Subject: [PATCH 1094/2524] xo-expression: + Lambda::type_str --- include/xo/expression/Lambda.hpp | 8 ++++++-- src/expression/Lambda.cpp | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index 7f5ebab4..06d5857f 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -23,8 +23,7 @@ namespace xo { **/ Lambda(const std::string & name, const std::vector & argv, - const ref::rp & body) - : Expression(exprtype::lambda), name_{name}, argv_{argv}, body_{body} {} + const ref::rp & body); /** downcast from Expression **/ static ref::brw from(ref::brw x) { @@ -32,6 +31,7 @@ namespace xo { } const std::string & name() const { return name_; } + const std::string & type_str() const { return type_str_; } const std::vector & argv() const { return argv_; } const ref::rp & body() const { return body_; } @@ -51,6 +51,10 @@ namespace xo { * so that they can be linked etc. **/ std::string name_; + /** e.g. + * "double(double,double)" for function of two doubles that returns a double + **/ + std::string type_str_; /** formal argument names **/ std::vector argv_; /** function body **/ diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index 87f70759..cae46f6f 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -4,7 +4,30 @@ #include "xo/indentlog/print/vector.hpp" namespace xo { + using std::stringstream; + namespace ast { + Lambda::Lambda(const std::string & name, + const std::vector & argv, + const ref::rp & body) + : Expression(exprtype::lambda), + name_{name}, + argv_{argv}, + body_{body} + { + stringstream ss; + ss << "double"; + ss << "("; + for (std::size_t i = 0; i < argv.size(); ++i) { + if (i > 0) + ss << ","; + ss << "double"; + } + ss << ")"; + + type_str_ = ss.str(); + } /*ctor*/ + void Lambda::display(std::ostream & os) const { os << " Date: Mon, 17 Jun 2024 17:14:44 -0400 Subject: [PATCH 1095/2524] xo-jit: + MachPipeline::dump_current_module --- include/xo/jit/MachPipeline.hpp | 3 +++ src/jit/MachPipeline.cpp | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index ca59dfb3..84b01a9d 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -92,6 +92,9 @@ namespace xo { **/ void machgen_current_module(); + /** dump text description of module contents to console **/ + void dump_current_module(); + /** lookup symbol in jit-associated output library **/ llvm::orc::ExecutorAddr lookup_symbol(const std::string & x); diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 969cfd04..3192e531 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -503,6 +503,14 @@ namespace xo { return nullptr; } /*codegen*/ + void + MachPipeline::dump_current_module() + { + /* dump module contents to console */ + + llvm_module_->dump(); + } + void MachPipeline::machgen_current_module() { From 93819cbb6c6b2093d6c2ced0ef7ff94fa2930df6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 17:16:07 -0400 Subject: [PATCH 1096/2524] xo-pyjit: README: bring example up-to-date --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e9f08673..342b3308 100644 --- a/README.md +++ b/README.md @@ -69,9 +69,9 @@ build an AST from within python ``` >>> x=make_var('x') # "x" a variable (context not yet known) >>> f1=make_sin_pm() # "sin()" ->>> c1=make_apply(f1,x) # "sin(x)" +>>> c1=make_apply(f1,[x]) # "sin(x)" >>> f2=make_cos_pm() # "cos()" ->>> c2=make_apply(f2,c1) # "cos(sin(x))" +>>> c2=make_apply(f2,[c1]) # "cos(sin(x))" >>> lm=make_lambda('foo', ['x'], c2) # "def foo(x): cos(sin(x))" >>> lm :argv "[ :argv \"[]\">]">> @@ -97,7 +97,7 @@ JITDylib "
" (ES: 0x0000000000446ee0, State = Open) Link order: [ ("
", MatchAllSymbols) ] Symbol table: "foo": [Callable] Never-Searched (Materializer 0x646fe0, xojit) ->>> fn=mp.lookup_dbl_dbl_fn('foo') +>>> fn=mp.lookup_dbl2dbl_fn('foo') >>> mp.dump_execution_session() JITDylib "
" (ES: 0x0000000000446ee0, State = Open) From 5ea46cf22e46c50b1d1c3c5e17d872eb02660c88 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 17:17:13 -0400 Subject: [PATCH 1097/2524] xo-pyjit: 1-arg and 2-arg double->double + double^2->double fns --- src/pyjit/pyjit.cpp | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 624f8882..2c76f219 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -7,15 +7,25 @@ #include namespace xo { - struct XferFn : public ref::Refcount { + struct XferDbl2DblFn : public ref::Refcount { using fptr_type = double (*) (double); - explicit XferFn(fptr_type fptr) : fptr_{fptr} {} + explicit XferDbl2DblFn(fptr_type fptr) : fptr_{fptr} {} double operator() (double x) { return (*fptr_)(x); } fptr_type fptr_; - }; + }; /*XferDbl2DblFn*/ + + struct XferDblDbl2DblFn : public ref::Refcount { + using fptr_type = double (*) (double, double); + + explicit XferDblDbl2DblFn(fptr_type fptr) : fptr_{fptr} {} + + double operator() (double x, double y) { return (*fptr_)(x, y); } + + fptr_type fptr_; + }; /*XferDblDbl2DblFn*/ namespace jit { using xo::ast::Expression; @@ -56,19 +66,36 @@ namespace xo { .def("machgen_current_module", &MachPipeline::machgen_current_module, py::doc("Make current module available for execution via the jit.\n" "Adds all functions generated since last call to this method.")) - .def("lookup_dbl_dbl_fn", + /* double -> double */ + .def("lookup_dbl2dbl_fn", [](MachPipeline & jit, const std::string & symbol) { auto llvm_addr = jit.lookup_symbol(symbol); auto fn_addr = llvm_addr.toPtr(); - return new XferFn(fn_addr); + return new XferDbl2DblFn(fn_addr); + }) + + /* (double x double) -> double */ + .def("lookup_dbldbl2dbl_fn", + [](MachPipeline & jit, const std::string & symbol) { + auto llvm_addr = jit.lookup_symbol(symbol); + + auto fn_addr = llvm_addr.toPtr(); + + return new XferDblDbl2DblFn(fn_addr); }) ; - py::class_>(m, "XferFn") + + py::class_>(m, "XferDbl2DblFn") .def("__call__", - [](XferFn & self, double x) { return self(x); } + [](XferDbl2DblFn & self, double x) { return self(x); } + ) + ; + py::class_>(m, "XferDblDbl2DblFn") + .def("__call__", + [](XferDblDbl2DblFn & self, double x, double y) { return self(x, y); } ) ; From 4acf521609810ca6241e07be112b20617b7a07eb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 17:17:46 -0400 Subject: [PATCH 1098/2524] xo-pyjit: use pycaller to streamline function pointer handling --- src/pyjit/pyjit.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 2c76f219..b446a2c9 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -3,6 +3,7 @@ #include "pyjit.hpp" #include "xo/pyexpression/pyexpression.hpp" #include "xo/jit/MachPipeline.hpp" +#include "xo/pyutil/pycaller.hpp" #include "xo/pyutil/pyutil.hpp" #include @@ -29,7 +30,10 @@ namespace xo { namespace jit { using xo::ast::Expression; + using xo::pyutil::pycaller_base; + using xo::pyutil::pycaller; using xo::ref::rp; + //using xo::ref::Refcount; using xo::ref::unowned_ptr; namespace py = pybind11; @@ -39,6 +43,8 @@ namespace xo { m.doc() = "pybind11 plugin for xo-jit"; + pycaller::declare_once(m); + pycaller::declare_once(m); py::class_>(m, "MachPipeline") .def_static("make", &MachPipeline::make, py::doc("Create machine pipeline for in-process code generation" @@ -85,6 +91,30 @@ namespace xo { return new XferDblDbl2DblFn(fn_addr); }) + + .def("lookup_fn", + [](MachPipeline & jit, const std::string & prototype, const std::string & symbol) -> pycaller_base* { + auto llvm_addr = jit.lookup_symbol(symbol); + + /* note: llvm_addr.toPtr<..> always succeeds, + * event if pointer refers to an object of incompatible type + * + * note: return value policy is for python to own the wrapper + */ + + if((prototype == "double(double,double)") || (prototype == "double(*)(double,double)")) { + auto fn_addr = llvm_addr.toPtr(); + + return new pycaller(fn_addr); + //return new XferDblDbl2DblFn(fn_addr); + } else if ((prototype == "double(double)") || (prototype == "double(*)(double)")) { + auto fn_addr = llvm_addr.toPtr(); + + return new pycaller(fn_addr); + } else { + throw std::runtime_error(tostr("MachPipeline.lookup_fn: unknown function prototype", + xtag("p", prototype))); + }}) ; From 80df3e013cefd21b9786ae351519abaec7e5db64 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 17:18:09 -0400 Subject: [PATCH 1099/2524] xo-pyjit: + MachPipeline.dump_current_module() --- src/pyjit/pyjit.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index b446a2c9..ad454e6d 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -45,6 +45,7 @@ namespace xo { pycaller::declare_once(m); pycaller::declare_once(m); + py::class_>(m, "MachPipeline") .def_static("make", &MachPipeline::make, py::doc("Create machine pipeline for in-process code generation" @@ -72,6 +73,9 @@ namespace xo { .def("machgen_current_module", &MachPipeline::machgen_current_module, py::doc("Make current module available for execution via the jit.\n" "Adds all functions generated since last call to this method.")) + .def("dump_current_module", &MachPipeline::dump_current_module, + py::doc("Dump contents of current module to console")) + /* double -> double */ .def("lookup_dbl2dbl_fn", [](MachPipeline & jit, const std::string & symbol) { From c8d5633d812d856c3c1bc4abfa0b49daa8792f46 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 17:18:24 -0400 Subject: [PATCH 1100/2524] xo-pyjit: README: + links --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 342b3308..ff4c99f4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # python bindings for llvm JIT for EGAD (xo-pyjit) +## Links + +- [cheatsheet for pyobject<->c++ conversion](https://github.com/pybind/pybind11/issues/1201) + ## Getting Started ### Build + install `xo-cmake` dependency From e169a0d276765f7daa196dd91d60ff105654bc02 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 17:41:50 -0400 Subject: [PATCH 1101/2524] xo-pyutil: + example (mostly to make LSP happy) --- CMakeLists.txt | 12 ++++-------- example/CMakeLists.txt | 1 + example/ex1/CMakeLists.txt | 11 +++++++++++ example/ex1/pyex1.cpp | 14 ++++++++++++++ example/ex1/pyutilexample.hpp.in | 21 +++++++++++++++++++++ 5 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 example/CMakeLists.txt create mode 100644 example/ex1/CMakeLists.txt create mode 100644 example/ex1/pyex1.cpp create mode 100644 example/ex1/pyutilexample.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 0143a02d..596c509c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,22 +26,18 @@ add_definitions(${PROJECT_CXX_FLAGS}) # ---------------------------------------------------------------- -#add_subdirectory(example) -#add_subdirectory(utest) - # ---------------------------------------------------------------- set(SELF_LIB xo_pyutil) xo_add_headeronly_library(${SELF_LIB}) -#xo_include_headeronly_options2(${SELF_LIB}) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) # ---------------------------------------------------------------- -# standard install + provide find_package() support -xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) - -xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) +add_subdirectory(example) +#add_subdirectory(utest) # ---------------------------------------------------------------- # install any additional components diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 00000000..4151ec21 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(ex1) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..d27232e4 --- /dev/null +++ b/example/ex1/CMakeLists.txt @@ -0,0 +1,11 @@ +# xo-pyutil/example/ex1/CMakeLists.txt + +set(SELF_LIB xo_pyutilexample) +set(SELF_SRCS pyex1.cpp) + +if (XO_ENABLE_EXAMPLES) + xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) + xo_pybind11_dependency(${SELF_LIB} refcnt) +endif() + +# end CMakeLists.txt diff --git a/example/ex1/pyex1.cpp b/example/ex1/pyex1.cpp new file mode 100644 index 00000000..385a5b70 --- /dev/null +++ b/example/ex1/pyex1.cpp @@ -0,0 +1,14 @@ +/* @file pyex1.cpp */ + +#include "pyutilexample.hpp" +#include "xo/pyutil/pyutil.hpp" +#include + +namespace xo { + + PYBIND11_MODULE(XO_PYUTILEXAMPLE_MODULE_NAME(), m) { + m.def("sqrt", [](double x) { return ::sqrt(x); }); + } +} + +/* end pyex1.cpp */ diff --git a/example/ex1/pyutilexample.hpp.in b/example/ex1/pyutilexample.hpp.in new file mode 100644 index 00000000..a094415a --- /dev/null +++ b/example/ex1/pyutilexample.hpp.in @@ -0,0 +1,21 @@ +/* @file pyutilexample.hpp */ + +/* python requires module name = library name + * example: + * PYBIND11_MODULE(XO_PYUTILEXAMPLE_MODULE_NAME(), m) { ... } + */ +#define XO_PYUTILEXAMPLE_MODULE_NAME() @SELF_LIB@ + +/* example: + * py::module_::import(XO_PYUTILEXAMPLE_MODULE_NAME_STR) + */ +#define XO_PYUTILEXAMPLE_MODULE_NAME_STR "@SELF_LIB@" + +/* example: + * XO_PYUTILEXAMPLE_IMPORT_MODULE() + * replaces + * py::module_::import("xo_pyexpression") + */ +#define XO_PYUTILEXAMPLE_IMPORT_MODULE() py::module_::import("@SELF_LIB@") + +/* end pyutilexample.hpp */ From 8549bd922313305ab6ab0a6ae279b34c84e1f0e6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 18:22:17 -0400 Subject: [PATCH 1102/2524] xo-unit: README: update to mention xo-build --- CMakeLists.txt | 3 --- README.md | 55 ++++++++++++++++++++++++++++++++------------------ 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6cfe5a99..50e70f0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,9 +22,6 @@ add_definitions(${PROJECT_CXX_FLAGS}) add_subdirectory(example) add_subdirectory(utest) -# ---------------------------------------------------------------- -# provide find_package() support for projects using this library - set(SELF_LIB xo_unit) xo_add_headeronly_library(${SELF_LIB}) xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) diff --git a/README.md b/README.md index 228f14b8..66792cea 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Provides compile-time dimension checking and scaling. -Similar to `boost::units`, but: +Similar in spirit to `boost::units`, but: 1. streamlined: assumes modern (c++20) support 2. supports fractional dimensions (rational powers) @@ -39,27 +39,41 @@ double x = a2.scale(); See [full install instructions](https://rconybea.github.io/web/xo-unit/html/install.html) for other installation strategies. -### build + install upstream dependencies +### build + install `xo-cmake` dependency - [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) -- [github/Rconybea/xo-flatstring](https://github.com/Rconybea/xo-flatstring) -- [github/Rconybea/xo-ratio](https://github.com/Rconybea/xo-ratio) -(Below assumes they're installed using some common value for `PREFIX`) +Installs a few cmake ingredients, along with build assistant `xo-build` for XO projects such as this one. -### fetch xo-unit +### build + install other XO dependencies +``` +$ xo-build --clone --configure --build --install xo-flatstring +$ xo-build --clone --configure --build --install xo-ratio +``` + +Note: can use `-n` to dry-run here + +### copy `xo-unit` repository locally +``` +$ xo-build --clone xo-unit +``` + +or equivalently ``` -$ cd ~/proj $ git clone https://github.com/rconybea/xo-unit ``` -### build + install +### build + install `xo-unit` +``` +$ xo-build --configure --build --install xo-unit +``` + +or equivalently: ``` -$ cd xo-unit $ PREFIX=/usr/local # or wherever you prefer -$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S . -B .build -$ cmake --build .build -j -$ cmake --install .build +$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-unit -B xo-unit/.build +$ cmake --build xo-unit/.build -j +$ cmake --install xo-unit/.build ``` ### build documentation @@ -71,24 +85,25 @@ When this completes, can point local browser to `xo-unit/.build/docs/sphinx/ind ### build for unit test coverage -Note that unit tests involve additional dependencies: -- [github/Rconybea/xo-indentlog](https://github.com/Rconybea/indentlog) -- [github/Rconybea/xo-randomgen](https://github.com/Rconybea/randomgen) +(Note that unit tests involve additional dependencies): +``` +$ xo-build --clone --configure --build --install xo-indentlog +$ xo-build --clone --configure --build --install xo-randomgen +``` ``` -$ cd xo-unit -$ mkdir .build-ccov -$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_INSTALL_PREFIX=${PREFIX} -DENABLE_TESTING=1 -B .build-ccov +$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_INSTALL_PREFIX=$PREFIX -DENABLE_TESTING=1 xo-unit/.build-ccov +$ cmake --build xo-unit/.build-ccov ``` run coverage-enabled unit tests: ``` -$ cmake --build .build-ccov -- test +$ cmake --build xo-unit/.build-ccov -- test ``` generate html+text coverage report: ``` -$ .build-ccov/gen-ccov +$ xo-unit/.build-ccov/gen-ccov ``` To see coverage, can point local browser to `xo-unit/.build-ccov/ccov/html/index.html` From 444ea0f4b5390d3a04aab61732ea0226fd95863d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 18:35:27 -0400 Subject: [PATCH 1103/2524] xo-pyjit: obsolete XferDbl2DblFn etc; prefer pycaller<> --- src/pyjit/pyjit.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index ad454e6d..6a935202 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -8,6 +8,7 @@ #include namespace xo { +#ifdef OBSOLETE struct XferDbl2DblFn : public ref::Refcount { using fptr_type = double (*) (double); @@ -27,6 +28,7 @@ namespace xo { fptr_type fptr_; }; /*XferDblDbl2DblFn*/ +#endif namespace jit { using xo::ast::Expression; @@ -76,6 +78,7 @@ namespace xo { .def("dump_current_module", &MachPipeline::dump_current_module, py::doc("Dump contents of current module to console")) +#ifdef OBSOLETE /* double -> double */ .def("lookup_dbl2dbl_fn", [](MachPipeline & jit, const std::string & symbol) { @@ -95,6 +98,7 @@ namespace xo { return new XferDblDbl2DblFn(fn_addr); }) +#endif .def("lookup_fn", [](MachPipeline & jit, const std::string & prototype, const std::string & symbol) -> pycaller_base* { @@ -122,6 +126,7 @@ namespace xo { ; +#ifdef OBSOLETE py::class_>(m, "XferDbl2DblFn") .def("__call__", [](XferDbl2DblFn & self, double x) { return self(x); } @@ -132,6 +137,7 @@ namespace xo { [](XferDblDbl2DblFn & self, double x, double y) { return self(x, y); } ) ; +#endif py::class_>(m, "llvm_Value") From debc2db34c701262db2f095924770fdfe95bbe3c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 19:33:44 -0400 Subject: [PATCH 1104/2524] xo-pyutil: pycaller: provide type-erased factory functions --- include/xo/pyutil/pycaller.hpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/include/xo/pyutil/pycaller.hpp b/include/xo/pyutil/pycaller.hpp index 8368c060..20ecc92c 100644 --- a/include/xo/pyutil/pycaller.hpp +++ b/include/xo/pyutil/pycaller.hpp @@ -12,6 +12,9 @@ namespace xo { namespace pyutil { struct pycaller_base { + using void_function_type = void (*)(); + using factory_function_type = pycaller_base*(*)(void_function_type); + virtual ~pycaller_base() = default; static pybind11::module & declare_once(pybind11::module & m) { @@ -40,8 +43,11 @@ namespace xo { struct pycaller : public pycaller_base { using self_type = pycaller; using function_type = Retval (*)(); + using void_function_type = void (*)(); - pycaller(function_type addr) : fptr_{reinterpret_cast(addr)} {} + pycaller(void_function_type addr) : fptr_{reinterpret_cast(addr)} {} + + static pycaller_base * make(void_function_type addr) { return new pycaller(addr); } static pybind11::module & declare_once(pybind11::module & m) { static bool s_once = false; @@ -68,8 +74,11 @@ namespace xo { struct pycaller : public pycaller_base { using self_type = pycaller; using function_type = Retval (*)(Arg1); + using void_function_type = void (*)(); - pycaller(function_type addr) : fptr_{reinterpret_cast(addr)} {} + pycaller(void_function_type addr) : fptr_{reinterpret_cast(addr)} {} + + static pycaller_base * make(void_function_type addr) { return new pycaller(addr); } static pybind11::module & declare_once(pybind11::module & m) { static bool s_once = false; @@ -99,8 +108,11 @@ namespace xo { struct pycaller : public pycaller_base { using self_type = pycaller; using function_type = Retval (*)(Arg1, Arg2); + using void_function_type = void (*)(); - pycaller(function_type addr) : fptr_{reinterpret_cast(addr)} {} + pycaller(void_function_type addr) : fptr_{reinterpret_cast(addr)} {} + + static pycaller_base * make(void_function_type addr) { return new pycaller(addr); } static pybind11::module & declare_once(pybind11::module & m) { static bool s_once = false; From c6da0fb58b4ae0b7dc63f1e98be3200cda526baf Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 19:35:02 -0400 Subject: [PATCH 1105/2524] xo-pyjit: + pycaller_store, streamlining c++ function signatures --- README.md | 4 +- src/pyjit/pyjit.cpp | 105 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 99 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ff4c99f4..6119b705 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,9 @@ build an AST from within python ``` >>> x=make_var('x') # "x" a variable (context not yet known) >>> f1=make_sin_pm() # "sin()" ->>> c1=make_apply(f1,[x]) # "sin(x)" +>>> c1=make_apply(f1,[x]) # "sin(x)" >>> f2=make_cos_pm() # "cos()" ->>> c2=make_apply(f2,[c1]) # "cos(sin(x))" +>>> c2=make_apply(f2,[c1]) # "cos(sin(x))" >>> lm=make_lambda('foo', ['x'], c2) # "def foo(x): cos(sin(x))" >>> lm :argv "[ :argv \"[]\">]">> diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 6a935202..1b53fdfb 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -39,14 +39,85 @@ namespace xo { using xo::ref::unowned_ptr; namespace py = pybind11; + /** storage for pycaller glue functions for different function signatures. + * each pycaller instance embodies captures a canonical (architecture-dependent) + * calling sequence for a C/C++ function with that signature. + **/ + struct pycaller_store { + public: + /** singleton instance **/ + static pycaller_store * instance() { return &s_instance; } + + /** establish caller for signature @p prototype_str. + * This needs to be called at most once for each distinct signature. + * + * Although it takes module as argument, the module being used + * doesn't (shoudn't ??) matter + **/ + template + pycaller_base::factory_function_type + require_prototype(py::module & m, + const std::string & prototype_str) + { + using caller_type = pycaller; + + caller_type::declare_once(m); + + /* factory function takes function pointer of type + * Retval(*)(Args...) + * and returns new instance of caller_type for that function + */ + + auto ix = pycaller_map_.find(prototype_str); + + auto retval = &caller_type::make; + + if(ix == pycaller_map_.end()) + pycaller_map_[prototype_str] = retval; + + return retval; + } + + /** lookup caller for signature @p prototype_str **/ + pycaller_base::factory_function_type + lookup_prototype(const std::string & prototype_str) const + { + auto ix = pycaller_map_.find(prototype_str); + + if (ix == pycaller_map_.end()) + return nullptr; + else + return ix->second; + } + + private: + static pycaller_store s_instance; + + /** map prototype string to pycaller factory for that prototype. + * For example + * "double(double)" -> pycaller() + **/ + std::unordered_map pycaller_map_; + + }; /*pycaller_store*/ + + pycaller_store + pycaller_store::s_instance; + PYBIND11_MODULE(XO_PYJIT_MODULE_NAME(), m) { // e.g. for xo::ast::Expression XO_PYEXPRESSION_IMPORT_MODULE(); // py::module_::import("pyexpression"); m.doc() = "pybind11 plugin for xo-jit"; - pycaller::declare_once(m); - pycaller::declare_once(m); + pycaller_store::instance() + ->require_prototype(m, "double(double)"); + pycaller_store::instance() + ->require_prototype(m, "double(double,double)"); + + //pycaller::declare_once(m); + //pycaller::declare_once(m); py::class_>(m, "MachPipeline") .def_static("make", &MachPipeline::make, @@ -104,25 +175,43 @@ namespace xo { [](MachPipeline & jit, const std::string & prototype, const std::string & symbol) -> pycaller_base* { auto llvm_addr = jit.lookup_symbol(symbol); + /* llvm doesn't know the actual function signature, + * so any function type will appear to succeed here. + * We cast to particular function type within the pycaller<..> template + */ + auto fn_addr = llvm_addr.toPtr(); + /* note: llvm_addr.toPtr<..> always succeeds, * event if pointer refers to an object of incompatible type * * note: return value policy is for python to own the wrapper + * + * note: pycaller signatures need to have been introduced in advance + * (in practice determined at compile time, + * since they encode a function-signature-specific calling sequence) + * by calling pycaller_store::instance()->require_prototype(prototype); */ + auto factory = pycaller_store::instance()->lookup_prototype(prototype); + + if (!factory) { + throw std::runtime_error(tostr("MachPipeline.lookup_fn: unknown function prototype", + xtag("p", prototype))); + } + + return (*factory)(fn_addr); + +#ifdef OBSOLETE if((prototype == "double(double,double)") || (prototype == "double(*)(double,double)")) { - auto fn_addr = llvm_addr.toPtr(); - return new pycaller(fn_addr); - //return new XferDblDbl2DblFn(fn_addr); } else if ((prototype == "double(double)") || (prototype == "double(*)(double)")) { - auto fn_addr = llvm_addr.toPtr(); - return new pycaller(fn_addr); } else { throw std::runtime_error(tostr("MachPipeline.lookup_fn: unknown function prototype", xtag("p", prototype))); - }}) + } +#endif + }) ; From 7243939916545eaf839238d4b1b1dcdbb9eb6ad3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 18 Jun 2024 14:05:51 -0400 Subject: [PATCH 1106/2524] xo-reflect: allow TypeDescr without .native_typeinfo --- README.md | 2 +- include/xo/reflect/Reflect.hpp | 9 ++++ include/xo/reflect/TaggedPtr.hpp | 2 +- include/xo/reflect/TypeDescr.hpp | 79 ++++++++++++++++++++++++++++---- src/reflect/TypeDescr.cpp | 4 +- 5 files changed, 83 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 528d10e0..370cbafb 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ or equivalently $ git clone git@github.com:Rconybea/xo-reflect.git ``` -### build + install xo-reflect +### build + install `xo-reflect` ``` $ xo-build --configure --build --install xo-reflect ``` diff --git a/include/xo/reflect/Reflect.hpp b/include/xo/reflect/Reflect.hpp index 20e862c9..e10519a8 100644 --- a/include/xo/reflect/Reflect.hpp +++ b/include/xo/reflect/Reflect.hpp @@ -227,6 +227,15 @@ namespace xo { return retval_td; } + /** given address @p src_address of a value with type described by @p src, + * return typed pointer of type @tparam T, provided that @p src_td + * actually describes @tparam T. Otherwise returns nullptr + **/ + template + static T * recover_native(TypeDescr src_td, void * src_address) { + return TaggedPtr(src_td, src_address).recover_native(); + } + /* Use: * T * xyz = ...; * TaggedPtr xyz_tp = Reflect::make_tp(xyz); diff --git a/include/xo/reflect/TaggedPtr.hpp b/include/xo/reflect/TaggedPtr.hpp index dce3fa06..fbffaa04 100644 --- a/include/xo/reflect/TaggedPtr.hpp +++ b/include/xo/reflect/TaggedPtr.hpp @@ -68,7 +68,7 @@ namespace xo { * to refer to a T-instance; otherwise nullptr */ template - T * recover_native() const { return this->td_->recover_native(this->address_); } + T * recover_native() const { return this->td_->recover_native2(this->td_, this->address_); } uint32_t n_child() const { return this->td_->n_child(this->address_); diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index 54013654..4d49876b 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -149,28 +149,66 @@ namespace xo { static void print_reflected_types(std::ostream & os); TypeId id() const { return id_; } - std::type_info const * typeinfo() const { return typeinfo_; } + std::type_info const * native_typeinfo() const { return native_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. + /* true iff the type represented by *this is the same as the type + * represented by T. * * Warning: comparing typeinfo address can give false negatives. * suspect this is caused by problems coalescing linker symbols * in the clang toolchain. */ template + [[deprecated]] bool is_native() const { - return ((this->typeinfo() == &typeid(T)) - || (this->typeinfo()->hash_code() == typeid(T).hash_code()) - || (this->typeinfo()->name() == typeid(T).name())); + if (this->native_typeinfo()) { + /* reminder: typeid(T).name() is 'interesting' but not intended + * to be human-readable. It's not how compiler labels + * a type for a human reader + */ + return ((this->native_typeinfo() == &typeid(T)) + || (this->native_typeinfo()->hash_code() == typeid(T).hash_code()) + || (this->native_typeinfo()->name() == typeid(T).name())); + } else { + /** if this type was established via Reflect::require(), + * then .canonical_name is computed by type_name() + * + * (see demangle.hh in xo-refcnt, which post-processes __PRETTY_FUNCTION__ + * or __FUNCSIG__) + * + * To manually construct an equivalent type, + * it's necessary to: + * 1. construct a unique and unambiguous canonical name for the type + * 2. be aware that type will only be recognized as equivalent to + * a natively-reflected type if canonical name matches exactly. + **/ + + /** FOR NOW: give up. **/ + throw std::runtime_error("TypeDescrBase::is_native: not implemented for manually-constructed TypeDescr objects. Prefer is_native2()"); + } } /*is_native*/ - /* safe downcast -- like dynamic_cast<>, but does not require a source type */ + /** safe downcast -- like dynamic_cast<>, but does not require a source type. + * + * TODO: need variation on this to correctly-handle function types, + * since for exampple cast from void* -> void (*)() is not allowed + * + * WARNING: relies on deprecated is_native(). Application code should prefer any of: + * 1. recover_native2(src_td, src_address) + * 2. Reflect::recover_native(src_td, src_address) + * 3. TaggedPtr(src_td,src_address).recover_native() + * instead of src_td->recover_native() + * + * (note: awkwardness here is that we don't have access to {Reflect.hpp, TaggedPtr.hpp} + * from this .hpp file, since TypeDescr.hpp is included by those headers) + **/ template + [[deprecated]] T * recover_native(void * address) const { if (this->is_native()) { return reinterpret_cast(address); @@ -179,6 +217,22 @@ namespace xo { } } /*recover_native*/ + /** safe downcast -- like dynamic_cast<>, but does not require a source type. + * + * Application code should prefer TaggedPtr::recover_native() + * + * TODO: need variation on this to correctly-handle function types, + * since for exampple cast from void* -> void (*)() is not allowed + **/ + template + T * recover_native2(TypeDescr address_td, void * address) const { + if (this == address_td) { + return reinterpret_cast(address); + } else { + return nullptr; + } + } /*recover_native2*/ + bool is_pointer() const { return this->tdextra_->is_pointer(); } bool is_vector() const { return this->tdextra_->is_vector(); } bool is_struct() const { return this->tdextra_->is_struct(); } @@ -255,7 +309,9 @@ namespace xo { * - s_type_table_map[TypeInfoRef(x->typeinfo())] = x */ - /* hashmap of all TypeDescr instances, indexed by . singleton */ + /* hashmap of all native TypeDescr instances, indexed by typeinfo. + * singleton. + */ static std::unordered_map> s_type_table_map; /* hashmap of (presumed) duplicate TypeInfoRef values. * This happens with clang sometimes when the same type is referenced @@ -269,8 +325,13 @@ namespace xo { private: /* unique id# for this type */ TypeId id_; - /* typeinfo for type T */ - std::type_info const * typeinfo_ = nullptr; + /** typeinfo for type T, if available. nullptr otherwise. + * + * 1. Always available for type-descriptions constructed via Reflect::require. + * 2. Always missing for manually-constructed TypeDescr instances, for example + * see Lambda.cpp in xo-expression. + **/ + std::type_info const * native_typeinfo_ = nullptr; /* canonical name for this type (see demangle.hpp for type_name()) * e.g. * xo::option::Px2 diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp index b0a0dfd7..8eaea8e0 100644 --- a/src/reflect/TypeDescr.cpp +++ b/src/reflect/TypeDescr.cpp @@ -124,11 +124,11 @@ namespace xo { } /*namespace*/ TypeDescrBase::TypeDescrBase(TypeId id, - std::type_info const * tinfo, + std::type_info const * native_tinfo, std::string_view canonical_name, std::unique_ptr tdextra) : id_{std::move(id)}, - typeinfo_{tinfo}, + native_typeinfo_{native_tinfo}, canonical_name_{std::move(canonical_name)}, short_name_{unqualified_name(canonical_name_)}, tdextra_{std::move(tdextra)} From dddd6ca5ec0d1f6e5773361d42652a10c49cfb74 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 18 Jun 2024 15:43:03 -0400 Subject: [PATCH 1107/2524] xo-reflect: refactor to support manually-constructed function types --- include/xo/reflect/EstablishTypeDescr.hpp | 2 +- include/xo/reflect/TypeDescr.hpp | 158 ++++++++++++++++---- include/xo/reflect/TypeDescrExtra.hpp | 2 + include/xo/reflect/function/FunctionTdx.hpp | 17 +-- src/reflect/TypeDescr.cpp | 158 ++++++++++++++------ src/reflect/function/FunctionTdx.cpp | 4 +- 6 files changed, 254 insertions(+), 87 deletions(-) diff --git a/include/xo/reflect/EstablishTypeDescr.hpp b/include/xo/reflect/EstablishTypeDescr.hpp index 13c80999..423345dc 100644 --- a/include/xo/reflect/EstablishTypeDescr.hpp +++ b/include/xo/reflect/EstablishTypeDescr.hpp @@ -40,7 +40,7 @@ namespace xo { template static TypeDescrW establish() { TypeDescrW td = TypeDescrBase::require(&typeid(T), - type_name(), + std::string(type_name()), nullptr); #ifdef NOT_USING diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index 4d49876b..a0c307be 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -116,6 +116,50 @@ namespace xo { } /*namespace detail*/ #endif + /* hashable contents of a FunctionTdx instance (without requiring decl of TypeDescrExtra), + * for unique-ification of manually-constructed function types + */ + struct FunctionTdxInfo { + FunctionTdxInfo() = default; + FunctionTdxInfo(TypeDescr retval_td, + const std::vector & arg_td_v, + bool is_noexcept) + : retval_td_{retval_td}, + arg_td_v_{arg_td_v}, + is_noexcept_{is_noexcept} + {} + + /** compare two FunctionTdxInfo objects for equality + **/ + inline bool operator==(const FunctionTdxInfo & other) const noexcept { + if (retval_td_ != other.retval_td_) + return true; + if (arg_td_v_.size() != other.arg_td_v_.size()) + return false; + + for (std::size_t i = 0, n = arg_td_v_.size(); i < n; ++i) { + if (arg_td_v_[i] != other.arg_td_v_[i]) + return false; + } + + if (is_noexcept_ != other.is_noexcept_) + return false; + + return true; + } + + /** function return value **/ + TypeDescr retval_td_ = nullptr; + /** function arguments, in positional order **/ + std::vector arg_td_v_; + /** true iff function promises never to throw **/ + bool is_noexcept_ = false; + }; + } +} + +namespace xo { + namespace reflect { class TypeDescrExtra; /* run-time description for a native c++ type */ @@ -130,8 +174,8 @@ namespace xo { * 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()); + return (s_native_type_table_map.find(TypeInfoRef(tinfo)) + != s_native_type_table_map.end()); } /*is_reflected*/ /* NOTE: @@ -141,17 +185,17 @@ namespace xo { * See FAQ * [Build Issues|Q2 - dynamic_cast> fails] */ - static TypeDescrW require(std::type_info const * tinfo, - std::string_view canonical_name, + static TypeDescrW require(const std::type_info * tinfo, + const std::string & canonical_name, std::unique_ptr tdextra); /* print table of reflected types to os */ static void print_reflected_types(std::ostream & os); TypeId id() const { return id_; } - std::type_info const * native_typeinfo() const { return native_typeinfo_; } - std::string_view const & canonical_name() const { return canonical_name_; } - std::string_view const & short_name() const { return short_name_; } + const std::type_info * native_typeinfo() const { return native_typeinfo_; } + const std::string & canonical_name() const { return canonical_name_; } + const std::string_view & short_name() const { return short_name_; } bool complete_flag() const { return complete_flag_; } TypeDescrExtra * tdextra() const { return tdextra_.get(); } Metatype metatype() const { return tdextra_->metatype(); } @@ -273,6 +317,8 @@ namespace xo { return *sm; } /*struct_member*/ + /** nullptr for non-function types **/ + const FunctionTdxInfo * fn_info() const { return this->tdextra_->fn_info(); } uint32_t n_fn_arg() const { return this->tdextra_->n_fn_arg(); } /* require: @@ -298,29 +344,62 @@ namespace xo { private: TypeDescrBase(TypeId id, - std::type_info const * tinfo, - std::string_view canonical_name, + const std::type_info * tinfo, + const std::string & canonical_name, std::unique_ptr tdextra); + void assign_native_tinfo(const std::type_info * tinfo) { + assert(!native_typeinfo_); + native_typeinfo_ = tinfo; + } + private: /* invariant: * - for all TypeDescrImpl instances x: * - s_type_table_v[x->id()] = x - * - s_type_table_map[TypeInfoRef(x->typeinfo())] = x + * - s_native_type_table_map[TypeInfoRef(x->typeinfo())] = x */ - /* hashmap of all native TypeDescr instances, indexed by typeinfo. - * singleton. - */ - static std::unordered_map> s_type_table_map; - /* hashmap of (presumed) duplicate TypeInfoRef values. - * This happens with clang sometimes when the same type is referenced - * from multiple modules (i.e. shared libs). - */ + /** vector of all TypeDescr instances, indexed by TypeId. singleton. **/ + static std::vector> s_type_table_v; + + /** hashmap of all TypeDescr instances, + * indexed by canonical_name. + * + * For manually-constructed TypeDescr instances + * (see xo-expression for use-case) we require: + * + * - TypeDescr::canonical_name uniquely identifies type + * - to interact with an actually-equivalent type T + * constructed by c++ compiler, we need + * to use the same canonical name that the compiler uses. + * + * See type xo::reflect::type_name<>() [in demangle.hpp under xo-refcnt] + * for implementation + **/ + static std::unordered_map s_canonical_type_table_map; + + /** hashmap of all native TypeDescr instances, + * indexed by typeinfo. singleton. + **/ + static std::unordered_map s_native_type_table_map; + + /** hashmap of (presumed) duplicate TypeInfoRef values. + * This happens with clang sometimes when the same type is referenced + * from multiple modules (i.e. shared libs). + **/ static std::unordered_map s_coalesced_type_table_map; - /* vector of all TypeDescr instances. singleton. */ - static std::vector s_type_table_v; + /** map from a vector of TypeDescr objects: + * [Retval, Arg1, ...Argn] + * to TypeDescr for function type + * Retval(*)(Arg1..Argn) + * + * Use these to unique-ify function types across: + * - types sourced natively from c++ compiler + * - types manually constructed (e.g. see Lambda.cpp in xo-expression) + **/ + static std::unordered_map s_function_type_map; private: /* unique id# for this type */ @@ -332,15 +411,20 @@ namespace xo { * see Lambda.cpp in xo-expression. **/ std::type_info const * native_typeinfo_ = nullptr; - /* canonical name for this type (see demangle.hpp for type_name()) + /** canonical name for this type (see demangle.hpp for type_name()) * e.g. * xo::option::Px2 - */ - std::string_view canonical_name_; - /* suffix of .canonical_name, just after last ':' - * e.g. - * Px2 - */ + * + * NOTE: if we only had to deal with types created via Reflect::reflect(), + * then canonical_name could be string_view. For manually-constructed + * types, there is no compiler-generated C-string constant to reference, + * so need to use std::string here + **/ + std::string canonical_name_; + /** substring .canonical_name, just after last ':' + * e.g. + * Px2 + **/ std::string_view short_name_; /* set to true once final value for .tdextra is established * intially all TypeDescr objects will use AtomicTdx for .tdextra @@ -380,4 +464,24 @@ namespace xo { } /*namespace reflect*/ } /*namespace xo*/ +namespace std { + /** @brief overload for hashing xo::reflect::FunctionTdxInfo objects + **/ + template <> + struct hash { + inline size_t operator()(const xo::reflect::FunctionTdxInfo & x) const noexcept { + /* we can hash on addresses, since TypeDescr objects are immutable */ + std::size_t h = hash{}(x.retval_td_); + + for (std::size_t i = 0, n = x.arg_td_v_.size(); i < n; ++i) { + h = (h << 1) ^ hash{}(x.arg_td_v_[i]); + } + + h = (h << 1) ^ (x.is_noexcept_ ? 1 : 0); + + return h; + } + }; +} + /* end TypeDescr.hpp */ diff --git a/include/xo/reflect/TypeDescrExtra.hpp b/include/xo/reflect/TypeDescrExtra.hpp index c16257b3..bef9463e 100644 --- a/include/xo/reflect/TypeDescrExtra.hpp +++ b/include/xo/reflect/TypeDescrExtra.hpp @@ -11,6 +11,7 @@ namespace xo { namespace reflect { /* forward-declaring here. see [reflect/struct/StructMember.hpp] */ class StructMember; + class FunctionTdxInfo; class TypeDescrBase; class TaggedPtr; @@ -67,6 +68,7 @@ namespace xo { * * @pre @ref TypeDescrExtra::is_function() is true **/ + virtual const FunctionTdxInfo * fn_info() const { return nullptr; } virtual const TypeDescrBase * fn_retval() const { return nullptr; } virtual uint32_t n_fn_arg() const { return 0; } virtual const TypeDescrBase * fn_arg(uint32_t /*i_arg*/) const { return nullptr; } diff --git a/include/xo/reflect/function/FunctionTdx.hpp b/include/xo/reflect/function/FunctionTdx.hpp index 9c5523a6..116f3721 100644 --- a/include/xo/reflect/function/FunctionTdx.hpp +++ b/include/xo/reflect/function/FunctionTdx.hpp @@ -32,10 +32,11 @@ namespace xo { virtual TaggedPtr child_tp(uint32_t i, void * object) const override; const std::string & struct_member_name(uint32_t i) const override; - virtual TypeDescr fn_retval() const override { return retval_td_; } - virtual uint32_t n_fn_arg() const override { return arg_td_v_.size(); } - virtual TypeDescr fn_arg(uint32_t i) const override { return arg_td_v_[i]; } - virtual bool fn_is_noexcept() const override { return is_noexcept_; } + virtual const FunctionTdxInfo * fn_info() const override { return &info_; } + virtual TypeDescr fn_retval() const override { return info_.retval_td_; } + virtual uint32_t n_fn_arg() const override { return info_.arg_td_v_.size(); } + virtual TypeDescr fn_arg(uint32_t i) const override { return info_.arg_td_v_[i]; } + virtual bool fn_is_noexcept() const override { return info_.is_noexcept_; } private: FunctionTdx(TypeDescr retval_td, @@ -43,12 +44,8 @@ namespace xo { std::vector arg_td_v); private: - /** function return value **/ - TypeDescr retval_td_; - /** function arguments, in positional order **/ - std::vector arg_td_v_; - /** true iff function promises never to throw **/ - bool is_noexcept_ = false; + /** ingredients in complete function type description **/ + FunctionTdxInfo info_; }; /*FunctionTdx*/ } /*namespace reflect*/ } /*namespace xo*/ diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp index 8eaea8e0..c541ea0a 100644 --- a/src/reflect/TypeDescr.cpp +++ b/src/reflect/TypeDescr.cpp @@ -15,69 +15,135 @@ namespace xo { uint32_t TypeId::s_next_id = 1; - std::unordered_map> - TypeDescrBase::s_type_table_map; + std::unordered_map + TypeDescrBase::s_function_type_map; + + std::unordered_map + TypeDescrBase::s_canonical_type_table_map; + + std::unordered_map + TypeDescrBase::s_native_type_table_map; std::unordered_map TypeDescrBase::s_coalesced_type_table_map; - std::vector + std::vector> TypeDescrBase::s_type_table_v; TypeDescrW - TypeDescrBase::require(std::type_info const * tinfo, - std::string_view canonical_name, + TypeDescrBase::require(const std::type_info * native_tinfo, + const std::string & canonical_name, std::unique_ptr tdextra) { - /* 1. lookup by tinfo hash_code in s_type_table_map */ - { - auto ix = s_type_table_map.find(TypeInfoRef(tinfo)); + if (native_tinfo) { + /* 1. lookup by tinfo hash_code in s_type_table_map + * Not available for manually-constructed type descriptions. + */ + { + auto ix = s_native_type_table_map.find(TypeInfoRef(native_tinfo)); - if ((ix != s_type_table_map.end()) && ix->second) - return ix->second.get(); - } + if ((ix != s_native_type_table_map.end()) && ix->second) + return ix->second; + } - /* 2. lookup by tinfo hash_code in s_coalesced_type_table_map */ - { - auto ix = s_coalesced_type_table_map.find(TypeInfoRef(tinfo)); + /* 2. lookup by tinfo hash_code in s_coalesced_type_table_map */ + { + auto ix = s_coalesced_type_table_map.find(TypeInfoRef(native_tinfo)); - if ((ix != s_coalesced_type_table_map.end()) && ix->second) - return ix->second; - } - - /* 3. 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; + if ((ix != s_coalesced_type_table_map.end()) && ix->second) + return ix->second; } } - TypeId id = TypeId::allocate(); + /* 3. lookup by canonical_name, before we create a new slot. + * + * Have to accept that on clang type_info objects aren't always unique (!$@#!!) + */ + { + auto ix = s_canonical_type_table_map.find(canonical_name); - std::unique_ptr & slot = s_type_table_map[TypeInfoRef(tinfo)]; + if (ix != s_canonical_type_table_map.end()) { + /** assume existing slot, with same canonical name, + * represents the same type as native_tinfo + **/ + if (native_tinfo) { + auto existing_tinfo = ix->second->native_typeinfo(); - slot.reset(new TypeDescrBase(id, - tinfo, - canonical_name, - std::move(tdextra))); + /* given we have a match: + * - on existing TypeDescr + * - with same canonical name as type assoc'd with native_tinfo + * then: + * it's possible existing TypeDescr was manually constructed + * (i.e. without capturing std::type_info). + * + * With that in mind, attach that typeinfo now + */ + if (!existing_tinfo) { + ix->second->assign_native_tinfo(native_tinfo); - if (s_type_table_v.size() <= id.id()) - s_type_table_v.resize(id.id() + 1); + s_native_type_table_map[TypeInfoRef(native_tinfo)] + = ix->second; + } - s_type_table_v[id.id()] = slot.get(); + if (existing_tinfo + && (existing_tinfo != native_tinfo)) + { + /* we have encountered distinct std::type_info objects + * that appear to represent the same type. + * (at least types with the same canonical name) + * + * We observe this happening sometimes with clang-prepared + * shared libraries; perhaps something going wrong with + * symbol coalescing. + * + * Store the dups in s_coalesced_type_table_map for future reference. + */ + auto jx = s_coalesced_type_table_map.find(TypeInfoRef(native_tinfo)); - return slot.get(); + if (jx == s_coalesced_type_table_map.end()) + s_coalesced_type_table_map[TypeInfoRef(native_tinfo)] + = ix->second; + } + } + + return ix->second; + } + } + + /* when control here: + * need type added to: + * - s_type_table_v + * - s_canonical_type_table_map + * - s_native_type_table_map + * - s_coalesced_type_table_map (omit, only used for dups) + * - s_function_type_map (if type represents a function) + */ + + /* allocate slot for a new TypeDescr instance: */ + + TypeId new_td_id = TypeId::allocate(); + + if (s_type_table_v.size() <= new_td_id.id()) + s_type_table_v.resize(new_td_id.id() + 1); + + auto & new_slot = s_type_table_v[new_td_id.id()]; + + auto new_td = new TypeDescrBase(new_td_id, + native_tinfo, + canonical_name, + std::move(tdextra)); + + new_slot.reset(new_td); + + s_canonical_type_table_map[std::string(new_slot->canonical_name())] = new_td; + if (native_tinfo) + s_native_type_table_map[TypeInfoRef(native_tinfo)] = new_td; + + if (new_td->tdextra() && new_td->is_function()) { + s_function_type_map[*(new_td->fn_info())] = new_td; + } + + return new_slot.get(); } /*require*/ void @@ -85,7 +151,7 @@ namespace xo { { os << "display(os); @@ -124,8 +190,8 @@ namespace xo { } /*namespace*/ TypeDescrBase::TypeDescrBase(TypeId id, - std::type_info const * native_tinfo, - std::string_view canonical_name, + const std::type_info * native_tinfo, + const std::string & canonical_name, std::unique_ptr tdextra) : id_{std::move(id)}, native_typeinfo_{native_tinfo}, diff --git a/src/reflect/function/FunctionTdx.cpp b/src/reflect/function/FunctionTdx.cpp index bfd43673..c81041ab 100644 --- a/src/reflect/function/FunctionTdx.cpp +++ b/src/reflect/function/FunctionTdx.cpp @@ -19,9 +19,7 @@ namespace xo { FunctionTdx::FunctionTdx(TypeDescr retval_td, bool is_noexcept, std::vector arg_td_v) - : retval_td_{retval_td}, - arg_td_v_{std::move(arg_td_v)}, - is_noexcept_{is_noexcept} + : info_{retval_td, std::move(arg_td_v), is_noexcept} { if (!retval_td) { throw std::runtime_error("FunctionTdx::ctor: null return type?"); From 9ff173f68a69590535625a748118a47ffb081839 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 18 Jun 2024 16:55:46 -0400 Subject: [PATCH 1108/2524] xo-expression: add explicit types to all Expressions --- include/xo/expression/Apply.hpp | 16 +++++--- include/xo/expression/Constant.hpp | 24 +++++++---- include/xo/expression/ConstantInterface.hpp | 2 +- include/xo/expression/Expression.hpp | 14 ++++++- include/xo/expression/IfExpr.hpp | 39 +++++++++++++----- include/xo/expression/Lambda.hpp | 27 ++++++++---- include/xo/expression/Primitive.hpp | 40 ++++++++++++------ include/xo/expression/PrimitiveInterface.hpp | 3 +- include/xo/expression/Variable.hpp | 20 +++++++-- src/expression/Apply.cpp | 22 ++++++++++ src/expression/IfExpr.cpp | 27 ++++++++++++ src/expression/Lambda.cpp | 43 +++++++++++++++++++- 12 files changed, 223 insertions(+), 54 deletions(-) diff --git a/include/xo/expression/Apply.hpp b/include/xo/expression/Apply.hpp index acb27a5f..775c31f9 100644 --- a/include/xo/expression/Apply.hpp +++ b/include/xo/expression/Apply.hpp @@ -20,11 +20,13 @@ namespace xo { **/ class Apply : public Expression { public: + using TypeDescr = xo::reflect::TypeDescr; + + public: + /** create new apply-expression instance + **/ static ref::rp make(const ref::rp & fn, - const std::vector> & argv) - { - return new Apply(fn, argv); - } + const std::vector> & argv); /** downcast from Expression **/ static ref::brw from(ref::brw x) { @@ -37,9 +39,11 @@ namespace xo { virtual void display(std::ostream & os) const; private: - Apply(const ref::rp & fn, + Apply(TypeDescr apply_valuetype, + const ref::rp & fn, const std::vector> & argv) - : Expression(exprtype::apply), fn_{fn}, argv_(argv) + : Expression(exprtype::apply, apply_valuetype), + fn_{fn}, argv_(argv) {} private: diff --git a/include/xo/expression/Constant.hpp b/include/xo/expression/Constant.hpp index 2741881f..17d8aa3b 100644 --- a/include/xo/expression/Constant.hpp +++ b/include/xo/expression/Constant.hpp @@ -29,13 +29,12 @@ namespace xo { using TypeDescr = xo::reflect::TypeDescr; public: - explicit Constant(const T & x) - : ConstantInterface(exprtype::constant), - value_td_{Reflect::require()}, - value_(x) - { - static_assert(std::is_standard_layout_v && std::is_trivial_v); - } + /** create constant expression representing literal value x **/ + static ref::rp make(const T & x) { + TypeDescr x_valuetype = Reflect::require(); + + return new Constant(x_valuetype, x); + } const T & value() const { return value_; } @@ -59,6 +58,15 @@ namespace xo { << ">"; } + private: + explicit Constant(TypeDescr x_type, const T & x) + : ConstantInterface(exprtype::constant, x_type), + value_td_{Reflect::require()}, + value_(x) + { + static_assert(std::is_standard_layout_v && std::is_trivial_v); + } + private: /** type description for T **/ TypeDescr value_td_; @@ -69,7 +77,7 @@ namespace xo { template ref::rp>> make_constant(const T & x) { - return new Constant(x); + return Constant::make(x); } } /*namespace ast*/ diff --git a/include/xo/expression/ConstantInterface.hpp b/include/xo/expression/ConstantInterface.hpp index 9bd38383..586322c7 100644 --- a/include/xo/expression/ConstantInterface.hpp +++ b/include/xo/expression/ConstantInterface.hpp @@ -22,7 +22,7 @@ namespace xo { public: /** @p extype sets expression-type; could be constant|primitive **/ - ConstantInterface(exprtype extype) : Expression{extype} {} + ConstantInterface(exprtype extype, TypeDescr valuetype) : Expression{extype, valuetype} {} /** downcast from Expression **/ static ref::brw from(ref::brw x) { diff --git a/include/xo/expression/Expression.hpp b/include/xo/expression/Expression.hpp index c9c4e0c0..22bd8d4a 100644 --- a/include/xo/expression/Expression.hpp +++ b/include/xo/expression/Expression.hpp @@ -5,6 +5,7 @@ #pragma once +#include "xo/reflect/TypeDescr.hpp" #include "xo/refcnt/Refcounted.hpp" #include "exprtype.hpp" @@ -23,12 +24,19 @@ namespace xo { * * Expressions are immutable. This means they can resused * across jit interactions + * + * Every expression evaluates to a value with a particular type **/ class Expression : public ref::Refcount { public: - explicit Expression(exprtype extype) : extype_{extype} {} + using TypeDescr = xo::reflect::TypeDescr; + + public: + explicit Expression(exprtype extype, TypeDescr valuetype) + : extype_{extype}, valuetype_{valuetype}{} exprtype extype() const { return extype_; } + TypeDescr valuetype() const { return valuetype_; } /** write human-readable representation to stream **/ virtual void display(std::ostream & os) const = 0; @@ -38,6 +46,10 @@ namespace xo { private: /** expression type (constant | apply | ..) for this expression **/ exprtype extype_ = exprtype::invalid; + /** type information (when available) for values produced by this + * expression. + **/ + TypeDescr valuetype_ = nullptr; }; /*Expression*/ inline std::ostream & diff --git a/include/xo/expression/IfExpr.hpp b/include/xo/expression/IfExpr.hpp index 592c585c..ff9c3e57 100644 --- a/include/xo/expression/IfExpr.hpp +++ b/include/xo/expression/IfExpr.hpp @@ -18,17 +18,16 @@ namespace xo { **/ class IfExpr : public Expression { public: - /** @p test test-expression; always execute - * @p when_true then-branch; executes only when test succeeds - * @p when_false else-branch; executes only when test fails + using TypeDescr = xo::reflect::TypeDescr; + + public: + /** create expression for conditional execution of + * @p when_true or @p when_false, depending on result + * of evaluating expression @p test **/ - IfExpr(const ref::rp & test, - const ref::rp & when_true, - const ref::rp & when_false) - : Expression(exprtype::ifexpr), - test_{test}, - when_true_{when_true}, - when_false_{when_false} {} + static ref::rp make(const ref::rp & test, + const ref::rp & when_true, + const ref::rp & when_false); /** downcast from Expression **/ static ref::brw from(ref::brw x) { @@ -43,6 +42,24 @@ namespace xo { virtual void display(std::ostream & os) const override; + private: + /** + * @p ifexpr_type type for value produced by if-expression. + * same as both when_true->valuetype() and + * when_false->valuetype(). + * @p test test-expression; always execute + * @p when_true then-branch; executes only when test succeeds + * @p when_false else-branch; executes only when test fails + **/ + IfExpr(TypeDescr ifexpr_type, + const ref::rp & test, + const ref::rp & when_true, + const ref::rp & when_false) + : Expression(exprtype::ifexpr, ifexpr_type), + test_{test}, + when_true_{when_true}, + when_false_{when_false} {} + private: /** if: * (if x y z) @@ -59,7 +76,7 @@ namespace xo { const ref::rp & when_true, const ref::rp & when_false) { - return new IfExpr(test, when_true, when_false); + return IfExpr::make(test, when_true, when_false); } } /*namespace ast*/ } /*namespace xo*/ diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index 06d5857f..ed88b44e 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -18,12 +18,14 @@ namespace xo { **/ class Lambda : public Expression { public: - /** @p argv Formal parameters, in left-to-right order + /** + * @p name Name for this lambda -- must be unique + * @p argv Formal parameters, in left-to-right order * @p body Expression for body of this function **/ - Lambda(const std::string & name, - const std::vector & argv, - const ref::rp & body); + static ref::rp make(const std::string & name, + const std::vector> & argv, + const ref::rp & body); /** downcast from Expression **/ static ref::brw from(ref::brw x) { @@ -32,7 +34,7 @@ namespace xo { const std::string & name() const { return name_; } const std::string & type_str() const { return type_str_; } - const std::vector & argv() const { return argv_; } + const std::vector> & argv() const { return argv_; } const ref::rp & body() const { return body_; } /** return number of arguments expected by this function **/ @@ -42,6 +44,15 @@ namespace xo { virtual void display(std::ostream & os) const override; + private: + /** @param lambda_type. function type for this lambda. + * We arbitrarily choose the form "Retval(*)(Args...)" + **/ + Lambda(const std::string & name, + TypeDescr lambda_type, + const std::vector> & argv, + const ref::rp & body); + private: /** lambda name. Initially supporting only form like * (define (foo x y z) @@ -56,17 +67,17 @@ namespace xo { **/ std::string type_str_; /** formal argument names **/ - std::vector argv_; + std::vector> argv_; /** function body **/ ref::rp body_; }; /*Lambda*/ inline ref::rp make_lambda(const std::string & name, - const std::vector & argv, + const std::vector> & argv, const ref::rp & body) { - return new Lambda(name, argv, body); + return Lambda::make(name, argv, body); } } /*namespace ast*/ } /*namespace xo*/ diff --git a/include/xo/expression/Primitive.hpp b/include/xo/expression/Primitive.hpp index f4fc21b7..507bee8e 100644 --- a/include/xo/expression/Primitive.hpp +++ b/include/xo/expression/Primitive.hpp @@ -20,6 +20,10 @@ namespace xo { * * In any case, a primitive serves as both declaration and definition * (May be possible to relax this to declaration-only using null value_ as sentinel..?) + * + * @tparam FunctionPointer a function-pointer type, e.g. double(*)(double). + * Must be in this "canonical form". std::function + * won't work here. **/ template class Primitive: public PrimitiveInterface { @@ -29,18 +33,12 @@ namespace xo { using TypeDescr = xo::reflect::TypeDescr; public: - Primitive(const std::string & name, - FunctionPointer fnptr) - : PrimitiveInterface(), - name_{name}, - value_td_{Reflect::require_function()}, - value_{fnptr} - { - if (!value_td_->is_function()) - throw std::runtime_error("Primitive: expected function pointer"); - if (!value_td_->fn_retval()) - throw std::runtime_error("Primitive: expected non-null function return value"); - } + static ref::rp make(const std::string & name, + FunctionPointer fnptr) { + TypeDescr fn_type = Reflect::require(); + + return new Primitive(fn_type, name, fnptr); + } FunctionPointer value() const { return value_; } @@ -69,6 +67,22 @@ namespace xo { << ">"; } + private: + Primitive(TypeDescr fn_type, + const std::string & name, + FunctionPointer fnptr) + : PrimitiveInterface(fn_type), + name_{name}, + value_td_{Reflect::require_function()}, + value_{fnptr} + { + if (!value_td_->is_function()) + throw std::runtime_error("Primitive: expected function pointer"); + if (!value_td_->fn_retval()) + throw std::runtime_error("Primitive: expected non-null function return value"); + } + + private: // from Expression: // exprtype extype_ @@ -85,7 +99,7 @@ namespace xo { template ref::rp> make_primitive(const std::string & name, FunctionPointer x) { - return new Primitive(name, x); + return Primitive::make(name, x); } } /*namespace ast*/ } /*namespace xo*/ diff --git a/include/xo/expression/PrimitiveInterface.hpp b/include/xo/expression/PrimitiveInterface.hpp index 3274195f..3798c329 100644 --- a/include/xo/expression/PrimitiveInterface.hpp +++ b/include/xo/expression/PrimitiveInterface.hpp @@ -14,7 +14,8 @@ namespace xo { namespace ast { class PrimitiveInterface : public ConstantInterface { public: - PrimitiveInterface() : ConstantInterface(exprtype::primitive) {} + PrimitiveInterface(TypeDescr fn_type) + : ConstantInterface(exprtype::primitive, fn_type) {} /** downcast from Expression **/ static ref::brw from(ref::brw x) { diff --git a/include/xo/expression/Variable.hpp b/include/xo/expression/Variable.hpp index fc565236..908efbf9 100644 --- a/include/xo/expression/Variable.hpp +++ b/include/xo/expression/Variable.hpp @@ -15,7 +15,14 @@ namespace xo { **/ class Variable : public Expression { public: - Variable(const std::string & name) : Expression(exprtype::variable), name_{name} {} + /** create expression representing a variable + * identified by @p name, that can take on values + * described by @p var_type. + **/ + static ref::rp make(const std::string & name, + TypeDescr var_type) { + return new Variable(name, var_type); + } /** downcast from Expression **/ static ref::brw from(ref::brw x) { @@ -26,14 +33,21 @@ namespace xo { virtual void display(std::ostream & os) const; + private: + Variable(const std::string & name, + TypeDescr var_type) + : Expression(exprtype::variable, var_type), + name_{name} {} + private: /** variable name **/ std::string name_; }; /*Variable*/ inline ref::rp - make_var(const std::string & name) { - return new Variable(name); + make_var(const std::string & name, + reflect::TypeDescr var_type) { + return Variable::make(name, var_type); } } /*namespace ast*/ } /*namespace xo*/ diff --git a/src/expression/Apply.cpp b/src/expression/Apply.cpp index 781ec170..12926ddb 100644 --- a/src/expression/Apply.cpp +++ b/src/expression/Apply.cpp @@ -4,7 +4,29 @@ #include "xo/indentlog/print/vector.hpp" namespace xo { + using xo::ref::rp; + namespace ast { + rp + Apply::make(const rp & fn, + const std::vector> & argv) + { + /* extract result type from function type */ + TypeDescr fn_valuetype = fn->valuetype(); + + if (!fn_valuetype->is_function()) { + throw std::runtime_error + (tostr("Apply::make: found expression F in function position," + " with value-type FT where a function type expected", + xtag("FT", fn_valuetype->short_name()), + xtag("F", fn_valuetype))); + } + + TypeDescr fn_retval_type = fn_valuetype->fn_retval(); + + return new Apply(fn_retval_type, fn, argv); + } + void Apply::display(std::ostream & os) const { os << " + IfExpr::make(const rp & test, + const rp & when_true, + const rp & when_false) + { + /** TODO: verify test returns _boolean_ type **/ + + if (when_true->valuetype() != when_false->valuetype()) { + throw std::runtime_error + (tostr("IfExpr::make:" + " types {T1,T2} found for branches of if-expr" + " where equal types expected", + xtag("T1", when_true->valuetype()->canonical_name()), + xtag("T2", when_false->valuetype()->canonical_name()))); + } + + /* arbitrary choice here */ + auto ifexpr_type = when_true->valuetype(); + + return new IfExpr(ifexpr_type, + test, + when_true, + when_false); + } /*make*/ + void IfExpr::display(std::ostream & os) const { os << " + Lambda::make(const std::string & name, + const std::vector> & argv, + const ref::rp & body) + { + using xo::reflect::FunctionTdx; + + /** assemble function type. + * + * NOTE: need this to be unique! + **/ + + std::vector arg_td_v; + arg_td_v.reserve(argv.size()); + + for (const auto & arg : argv) { + arg_td_v.push_back(arg->valuetype()); + } + + auto function_info + = FunctionTdxInfo(body->valuetype(), + arg_td_v, + false /*!is_noexcept*/); + + TypeDescr lambda_td + = TypeDescrBase::require_by_fn_info(function_info); + + return new Lambda(name, + lambda_td, + argv, + body); + } /*make*/ + Lambda::Lambda(const std::string & name, - const std::vector & argv, + TypeDescr lambda_type, + const std::vector> & argv, const ref::rp & body) - : Expression(exprtype::lambda), + : Expression(exprtype::lambda, lambda_type), name_{name}, argv_{argv}, body_{body} From 20eab4e5d3152210e27ca4546a7b2cf1967f9eb7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 18 Jun 2024 16:56:14 -0400 Subject: [PATCH 1109/2524] xo-expression: ++ docs --- LESSONS | 9 +++++++++ TODO | 3 +++ 2 files changed, 12 insertions(+) create mode 100644 LESSONS create mode 100644 TODO diff --git a/LESSONS b/LESSONS new file mode 100644 index 00000000..c719f02e --- /dev/null +++ b/LESSONS @@ -0,0 +1,9 @@ +* want reflection to work without requiring std::type_info, + so that in xo-expression we can create function types that + C++ compiler has never encountered. + +** This means we need the ability to construct the canonical name + for a type without c++ compiler's assistance. + +** need lookup using function ingredients (return types + arg types + noexcept) + to intern lambda types diff --git a/TODO b/TODO new file mode 100644 index 00000000..c2a91ce8 --- /dev/null +++ b/TODO @@ -0,0 +1,3 @@ +- In xo-reflect, TypeDescr objects are immortal. + In original context rationale was recording information for already-established C++ types. + Maybe want to revisit that choice when associating TypeDescr objects with expressions? From 8a20796fe9156add78deda060dbc6fb0d48a4139 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 18 Jun 2024 17:14:38 -0400 Subject: [PATCH 1110/2524] xo-reflect: + Reflect::is_native() --- include/xo/reflect/Reflect.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/xo/reflect/Reflect.hpp b/include/xo/reflect/Reflect.hpp index e10519a8..23179c13 100644 --- a/include/xo/reflect/Reflect.hpp +++ b/include/xo/reflect/Reflect.hpp @@ -227,6 +227,12 @@ namespace xo { return retval_td; } + /** true iff @p src_td is a type-description for @tparam T **/ + template + static bool is_native(TypeDescr src_td) { + return (require() == src_td); + } + /** given address @p src_address of a value with type described by @p src, * return typed pointer of type @tparam T, provided that @p src_td * actually describes @tparam T. Otherwise returns nullptr From 6fc1fff9dc6ef6432c02e6474a2c3b24b05a6c32 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 18 Jun 2024 17:15:08 -0400 Subject: [PATCH 1111/2524] xo-reflect: + TypeDescr::lookup_by_name() --- include/xo/reflect/TypeDescr.hpp | 2 ++ src/reflect/TypeDescr.cpp | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index a0c307be..cd596979 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -190,6 +190,8 @@ namespace xo { std::unique_ptr tdextra); /* print table of reflected types to os */ + /** lookup type by canonical name **/ + static TypeDescr lookup_by_name(const std::string & canonical_name); static void print_reflected_types(std::ostream & os); TypeId id() const { return id_; } diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp index c541ea0a..7c2eb19f 100644 --- a/src/reflect/TypeDescr.cpp +++ b/src/reflect/TypeDescr.cpp @@ -146,6 +146,19 @@ namespace xo { return new_slot.get(); } /*require*/ + TypeDescr + TypeDescrBase::lookup_by_name(const std::string & name) { + auto ix = s_canonical_type_table_map.find(name); + + if (ix == s_canonical_type_table_map.end()) { + throw std::runtime_error(tostr("TypeDescrBase::lookup_by_name" + ": no registered type with canonical name T", + xtag("T", name))); + } + + return ix->second; + } /*lookup_by_name*/ + void TypeDescrBase::print_reflected_types(std::ostream & os) { From f4db0eefce141f7943540bc41051cfbe3a974da7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 18 Jun 2024 17:16:05 -0400 Subject: [PATCH 1112/2524] xo-reflect: + FunctionTdxInfo::make_canonical_name() --- include/xo/reflect/TypeDescr.hpp | 13 ++++++++----- src/reflect/TypeDescr.cpp | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index cd596979..77303a83 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -148,18 +148,21 @@ namespace xo { return true; } + /** construct canonical description for this type + * will be like + * Retval(*)(Arg1,..,Argn) + **/ + std::string make_canonical_name() const; + + public: /** function return value **/ TypeDescr retval_td_ = nullptr; /** function arguments, in positional order **/ std::vector arg_td_v_; /** true iff function promises never to throw **/ bool is_noexcept_ = false; - }; - } -} + }; /*FunctionTdxInfo*/ -namespace xo { - namespace reflect { class TypeDescrExtra; /* run-time description for a native c++ type */ diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp index 7c2eb19f..84579d5b 100644 --- a/src/reflect/TypeDescr.cpp +++ b/src/reflect/TypeDescr.cpp @@ -15,6 +15,25 @@ namespace xo { uint32_t TypeId::s_next_id = 1; + std::string + FunctionTdxInfo::make_canonical_name() const + { + std::ostringstream ss; + + ss << retval_td_->canonical_name(); + ss << "(*)("; + for (std::size_t i = 0, n = arg_td_v_.size(); i < n; ++i) { + if (i > 0) + ss << ","; + ss << arg_td_v_[i]->canonical_name(); + } + ss << ")"; + + return ss.str(); + } /*make_canonical_name*/ + + // ----- TypeDescrBase ----- + std::unordered_map TypeDescrBase::s_function_type_map; From 276e1e545115334b6d9283cb99c3687a00f41082 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 18 Jun 2024 17:16:28 -0400 Subject: [PATCH 1113/2524] xo-reflect: + TypeDescrBase::require_by_fn_info() --- include/xo/reflect/TypeDescr.hpp | 4 +++- src/reflect/TypeDescr.cpp | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index 77303a83..4452f094 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -192,7 +192,9 @@ namespace xo { const std::string & canonical_name, std::unique_ptr tdextra); - /* print table of reflected types to os */ + /** Create type-description for function from input ingredients. **/ + static TypeDescrW require_by_fn_info(const FunctionTdxInfo & fn_info); + /** lookup type by canonical name **/ static TypeDescr lookup_by_name(const std::string & canonical_name); static void print_reflected_types(std::ostream & os); diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp index 84579d5b..c9812240 100644 --- a/src/reflect/TypeDescr.cpp +++ b/src/reflect/TypeDescr.cpp @@ -165,6 +165,20 @@ namespace xo { return new_slot.get(); } /*require*/ + TypeDescrW + TypeDescrBase::require_by_fn_info(const FunctionTdxInfo & fn_info) { + auto ix = s_function_type_map.find(fn_info); + + if (ix != s_function_type_map.end()) + return ix->second; + + auto fn_tdextra = FunctionTdx::make_function(fn_info); + + return require(nullptr /*native_tinfo - n/avail on this path*/, + fn_info.make_canonical_name(), + std::move(fn_tdextra)); + } /*require_by_fn_info*/ + TypeDescr TypeDescrBase::lookup_by_name(const std::string & name) { auto ix = s_canonical_type_table_map.find(name); From ef41f9b28074a5715d7886e09d373f209928cab4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 18 Jun 2024 17:17:09 -0400 Subject: [PATCH 1114/2524] xo-reflect: + comment --- include/xo/reflect/TypeDescr.hpp | 2 ++ src/reflect/TypeDescr.cpp | 1 + 2 files changed, 3 insertions(+) diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index 4452f094..c85b2379 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -197,6 +197,8 @@ namespace xo { /** lookup type by canonical name **/ static TypeDescr lookup_by_name(const std::string & canonical_name); + + /** print table of reflected types to os **/ static void print_reflected_types(std::ostream & os); TypeId id() const { return id_; } diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp index c9812240..2ffb59a2 100644 --- a/src/reflect/TypeDescr.cpp +++ b/src/reflect/TypeDescr.cpp @@ -4,6 +4,7 @@ #include "TaggedPtr.hpp" #include "TypeDescrExtra.hpp" #include "atomic/AtomicTdx.hpp" +#include "function/FunctionTdx.hpp" #include "xo/indentlog/scope.hpp" namespace xo { From b06c55b98b73aea001264d56e1dd821a6837ba10 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 18 Jun 2024 17:17:26 -0400 Subject: [PATCH 1115/2524] xo-reflect: + FunctionTdx::make_function() --- include/xo/reflect/function/FunctionTdx.hpp | 8 +++++--- src/reflect/function/FunctionTdx.cpp | 20 ++++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/include/xo/reflect/function/FunctionTdx.hpp b/include/xo/reflect/function/FunctionTdx.hpp index 116f3721..5d2f57c2 100644 --- a/include/xo/reflect/function/FunctionTdx.hpp +++ b/include/xo/reflect/function/FunctionTdx.hpp @@ -24,6 +24,10 @@ namespace xo { static std::unique_ptr make_function(TypeDescr retval_td, std::vector arg_td_v, bool is_noexcept); + /** create instance from FunctionTdxInfo + * @param fn_info. function ingredients -- return type, arg types, noexcept + **/ + static std::unique_ptr make_function(const FunctionTdxInfo & fn_info); // ----- Inherited from TypeDescrExtra ----- @@ -39,9 +43,7 @@ namespace xo { virtual bool fn_is_noexcept() const override { return info_.is_noexcept_; } private: - FunctionTdx(TypeDescr retval_td, - bool is_noexcept, - std::vector arg_td_v); + FunctionTdx(const FunctionTdxInfo & fn_info); private: /** ingredients in complete function type description **/ diff --git a/src/reflect/function/FunctionTdx.cpp b/src/reflect/function/FunctionTdx.cpp index c81041ab..27c0f8fc 100644 --- a/src/reflect/function/FunctionTdx.cpp +++ b/src/reflect/function/FunctionTdx.cpp @@ -11,17 +11,21 @@ namespace xo { std::vector arg_td_v, bool is_noexcept) { - return std::unique_ptr(new FunctionTdx(retval_td, - is_noexcept, - std::move(arg_td_v))); + return make_function(FunctionTdxInfo(retval_td, + std::move(arg_td_v), + is_noexcept)); } - FunctionTdx::FunctionTdx(TypeDescr retval_td, - bool is_noexcept, - std::vector arg_td_v) - : info_{retval_td, std::move(arg_td_v), is_noexcept} + std::unique_ptr + FunctionTdx::make_function(const FunctionTdxInfo & fn_info) { - if (!retval_td) { + return std::unique_ptr(new FunctionTdx(fn_info)); + } + + FunctionTdx::FunctionTdx(const FunctionTdxInfo & fn_info) + : info_{fn_info} + { + if (!info_.retval_td_) { throw std::runtime_error("FunctionTdx::ctor: null return type?"); } } From 0595c7c74e3806f5e1783e9d6dc361543aee3c63 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 18 Jun 2024 17:23:32 -0400 Subject: [PATCH 1116/2524] xo-expression: refactor: enforce Lambda.argv must contain Variables --- include/xo/expression/Lambda.hpp | 11 ++++++----- src/expression/Lambda.cpp | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index ed88b44e..cdf72ed9 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -6,6 +6,7 @@ #pragma once #include "Expression.hpp" +#include "Variable.hpp" #include #include //#include @@ -24,7 +25,7 @@ namespace xo { * @p body Expression for body of this function **/ static ref::rp make(const std::string & name, - const std::vector> & argv, + const std::vector> & argv, const ref::rp & body); /** downcast from Expression **/ @@ -34,7 +35,7 @@ namespace xo { const std::string & name() const { return name_; } const std::string & type_str() const { return type_str_; } - const std::vector> & argv() const { return argv_; } + const std::vector> & argv() const { return argv_; } const ref::rp & body() const { return body_; } /** return number of arguments expected by this function **/ @@ -50,7 +51,7 @@ namespace xo { **/ Lambda(const std::string & name, TypeDescr lambda_type, - const std::vector> & argv, + const std::vector> & argv, const ref::rp & body); private: @@ -67,14 +68,14 @@ namespace xo { **/ std::string type_str_; /** formal argument names **/ - std::vector> argv_; + std::vector> argv_; /** function body **/ ref::rp body_; }; /*Lambda*/ inline ref::rp make_lambda(const std::string & name, - const std::vector> & argv, + const std::vector> & argv, const ref::rp & body) { return Lambda::make(name, argv, body); diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index acc40cbe..d92ac801 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -14,7 +14,7 @@ namespace xo { namespace ast { rp Lambda::make(const std::string & name, - const std::vector> & argv, + const std::vector> & argv, const ref::rp & body) { using xo::reflect::FunctionTdx; @@ -47,7 +47,7 @@ namespace xo { Lambda::Lambda(const std::string & name, TypeDescr lambda_type, - const std::vector> & argv, + const std::vector> & argv, const ref::rp & body) : Expression(exprtype::lambda, lambda_type), name_{name}, From c381fc47517c1811bba847cdb7ac8b07a434e8db Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 18 Jun 2024 17:26:14 -0400 Subject: [PATCH 1117/2524] xo-pyexpression: + typedescr arg to make_var() --- src/pyexpression/pyexpression.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index bc58917d..283df9ac 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -137,7 +137,9 @@ namespace xo { .def_property_readonly("name", &Variable::name, py::doc("variable name")) ; - m.def("make_var", &make_var); + m.def("make_var", &make_var, + py::arg("name"), + py::arg("var_type")); // ----- Lambdas ----- From 05c94a3105961e645efa7ba96f654c6d782da3a5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 18 Jun 2024 17:30:56 -0400 Subject: [PATCH 1118/2524] xo-reflect: fix constructed canonical name for fptr --- src/reflect/TypeDescr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp index 2ffb59a2..34cfa8e1 100644 --- a/src/reflect/TypeDescr.cpp +++ b/src/reflect/TypeDescr.cpp @@ -22,7 +22,7 @@ namespace xo { std::ostringstream ss; ss << retval_td_->canonical_name(); - ss << "(*)("; + ss << " (*)("; for (std::size_t i = 0, n = arg_td_v_.size(); i < n; ++i) { if (i > 0) ss << ","; From 57a75e380fd5e7bfec867d18f34d6e512c737c1c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 10:51:48 -0400 Subject: [PATCH 1119/2524] xo-reflect: + reflect builtin c++ types (int ..) automatically --- include/xo/reflect/TypeDescr.hpp | 15 +++++++++++++++ src/reflect/TypeDescr.cpp | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index c85b2379..31c58063 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -200,6 +200,8 @@ namespace xo { /** print table of reflected types to os **/ static void print_reflected_types(std::ostream & os); + /** print table of function types to os **/ + static void print_function_types(std::ostream & os); TypeId id() const { return id_; } const std::type_info * native_typeinfo() const { return native_typeinfo_; } @@ -470,6 +472,19 @@ namespace xo { return os; } + class TypeDescrTable { + public: + TypeDescrTable * instance() { return &s_instance; } + + private: + /** initialize with builtin atomic types: + * float, double, char, short, int, long, bool + **/ + TypeDescrTable(); + + private: + static TypeDescrTable s_instance; + }; } /*namespace reflect*/ } /*namespace xo*/ diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp index 34cfa8e1..41f5204e 100644 --- a/src/reflect/TypeDescr.cpp +++ b/src/reflect/TypeDescr.cpp @@ -3,6 +3,7 @@ #include "TypeDescr.hpp" #include "TaggedPtr.hpp" #include "TypeDescrExtra.hpp" +#include "Reflect.hpp" #include "atomic/AtomicTdx.hpp" #include "function/FunctionTdx.hpp" #include "xo/indentlog/scope.hpp" @@ -301,6 +302,20 @@ namespace xo { this->complete_flag_ = true; this->tdextra_ = std::move(tdx); } /*assign_tdextra*/ + + TypeDescrTable::TypeDescrTable() { + Reflect::require(); + Reflect::require(); + Reflect::require(); + Reflect::require(); + Reflect::require(); + Reflect::require(); + Reflect::require(); + Reflect::require(); + } /*ctor*/ + + TypeDescrTable + TypeDescrTable::s_instance; } /*namespace reflect*/ } /*namespace xo*/ From 5c4b062cf85eadc1cafdc7dd728a6b602fb8d73c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 10:53:04 -0400 Subject: [PATCH 1120/2524] xo-expression: refactor: Lambda/Primitive share FunctionInterface --- include/xo/expression/FunctionInterface.hpp | 34 ++++++++++++++++++++ include/xo/expression/Lambda.hpp | 11 +++++-- include/xo/expression/Primitive.hpp | 20 +++++++----- include/xo/expression/PrimitiveInterface.hpp | 17 ++++------ src/expression/Lambda.cpp | 2 +- src/expression/Variable.cpp | 1 + 6 files changed, 63 insertions(+), 22 deletions(-) create mode 100644 include/xo/expression/FunctionInterface.hpp diff --git a/include/xo/expression/FunctionInterface.hpp b/include/xo/expression/FunctionInterface.hpp new file mode 100644 index 00000000..a51dc69b --- /dev/null +++ b/include/xo/expression/FunctionInterface.hpp @@ -0,0 +1,34 @@ +/** @file FunctionInterface.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "Expression.hpp" +//#include + +namespace xo { + namespace ast { + class FunctionInterface : public Expression { + public: + FunctionInterface(exprtype extype, TypeDescr fn_type) + : Expression(extype, fn_type) {} + + /** downcast from Expression **/ + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + virtual const std::string & name() const = 0; + virtual int n_arg() const = 0; // { return this->value_td()->n_fn_arg(); } + virtual TypeDescr fn_retval() const = 0; // { return this->value_td()->fn_retval(); } + virtual TypeDescr fn_arg(uint32_t i) const = 0; // { return this->value_td()->fn_arg(i); } + + private: + }; /*FunctionInterface*/ + } /*namespace ast*/ +} /*namespace xo*/ + + +/** end FunctionInterface.hpp **/ diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index cdf72ed9..ce63860d 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -6,6 +6,7 @@ #pragma once #include "Expression.hpp" +#include "FunctionInterface.hpp" #include "Variable.hpp" #include #include @@ -17,7 +18,7 @@ namespace xo { * @brief abstract syntax tree for a function definition * **/ - class Lambda : public Expression { + class Lambda : public FunctionInterface { public: /** * @p name Name for this lambda -- must be unique @@ -33,13 +34,17 @@ namespace xo { return ref::brw::from(x); } - const std::string & name() const { return name_; } const std::string & type_str() const { return type_str_; } const std::vector> & argv() const { return argv_; } const ref::rp & body() const { return body_; } + // ----- FunctionInterface ----- + + virtual const std::string & name() const override { return name_; } /** return number of arguments expected by this function **/ - int n_arg() const { return argv_.size(); } + virtual int n_arg() const override { return argv_.size(); } + virtual TypeDescr fn_retval() const override { return body_->valuetype(); } + virtual TypeDescr fn_arg(uint32_t i) const override { return argv_[i]->valuetype(); } // ----- Expression ----- diff --git a/include/xo/expression/Primitive.hpp b/include/xo/expression/Primitive.hpp index 507bee8e..f251465d 100644 --- a/include/xo/expression/Primitive.hpp +++ b/include/xo/expression/Primitive.hpp @@ -6,6 +6,7 @@ #pragma once #include "PrimitiveInterface.hpp" +#include "xo/reflect/Reflect.hpp" //#include namespace xo { @@ -42,14 +43,8 @@ namespace xo { FunctionPointer value() const { return value_; } - // ----- PrimitiveInterface ----- - - virtual std::string const & name() const override { return name_; } - - // ----- ConstantInterface ----- - - virtual TypeDescr value_td() const override { return value_td_; } - virtual TaggedPtr value_tp() const override { + TypeDescr value_td() const { return value_td_; } + TaggedPtr value_tp() const { /* note: idk why, but need to spell this out in two steps with gcc 13.2 */ const void * erased_cptr = &value_; void * erased_ptr = const_cast(erased_cptr); @@ -57,6 +52,15 @@ namespace xo { return TaggedPtr(value_td_, erased_ptr); } + // ----- PrimitiveInterface ----- + + // ----- FunctionInterface ----- + + virtual std::string const & name() const override { return name_; } + virtual int n_arg() const override { return this->value_td()->n_fn_arg(); } + virtual TypeDescr fn_retval() const override { return this->value_td()->fn_retval(); } + virtual TypeDescr fn_arg(uint32_t i) const override { return this->value_td()->fn_arg(i); } + // ----- Expression ----- virtual void display(std::ostream & os) const override { diff --git a/include/xo/expression/PrimitiveInterface.hpp b/include/xo/expression/PrimitiveInterface.hpp index 3798c329..685d04ae 100644 --- a/include/xo/expression/PrimitiveInterface.hpp +++ b/include/xo/expression/PrimitiveInterface.hpp @@ -5,30 +5,27 @@ #pragma once -#include "ConstantInterface.hpp" +#include "FunctionInterface.hpp" //#include #include namespace xo { namespace ast { - class PrimitiveInterface : public ConstantInterface { + class PrimitiveInterface : public FunctionInterface { public: PrimitiveInterface(TypeDescr fn_type) - : ConstantInterface(exprtype::primitive, fn_type) {} + : FunctionInterface(exprtype::primitive, fn_type) {} /** downcast from Expression **/ static ref::brw from(ref::brw x) { return ref::brw::from(x); } - virtual const std::string & name() const = 0; - int n_arg() const { return this->value_td()->n_fn_arg(); } - TypeDescr fn_retval() const { return this->value_td()->fn_retval(); } - TypeDescr fn_arg(uint32_t i) const { return this->value_td()->fn_arg(i); } - - //virtual TypeDescr value_td() const override = 0; - //virtual TaggedPtr value_tp() const override = 0; + // virtual const std::string & name() const; + // virtual int n_arg() const; + // virtual TypeDescr fn_retval() const; + // virtual TypeDescr fn_arg(uint32_t i) const; private: }; /*PrimitiveInterface*/ diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index d92ac801..4cd25da9 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -49,7 +49,7 @@ namespace xo { TypeDescr lambda_type, const std::vector> & argv, const ref::rp & body) - : Expression(exprtype::lambda, lambda_type), + : FunctionInterface(exprtype::lambda, lambda_type), name_{name}, argv_{argv}, body_{body} diff --git a/src/expression/Variable.cpp b/src/expression/Variable.cpp index c8ab19fc..52890ccc 100644 --- a/src/expression/Variable.cpp +++ b/src/expression/Variable.cpp @@ -8,6 +8,7 @@ namespace xo { Variable::display(std::ostream & os) const { os << "valuetype()->short_name()) << ">"; } /*display*/ } /*namespace ast*/ From d43ac46eab14303fe0234abd36dc9ce75ef218b7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 10:54:06 -0400 Subject: [PATCH 1121/2524] xo-jit: example: + reqd type arg to Variable in ex1.cpp --- example/ex1/ex1.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 01d6073b..3929bcfa 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -41,6 +41,7 @@ main() { using xo::ast::make_apply; using xo::ast::make_var; using xo::ast::make_lambda; + using xo::reflect::Reflect; using xo::xtag; using std::cerr; using std::endl; @@ -128,13 +129,13 @@ main() { auto sin = make_primitive("sin", ::sin); auto cos = make_primitive("cos", ::cos); - auto x_var = make_var("x"); + auto x_var = make_var("x", Reflect::require()); auto call1 = make_apply(cos, {x_var}); /* (cos x) */ auto call2 = make_apply(sin, {call1}); /* (sin (cos x)) */ /* (define (lm_1 x) (sin (cos x))) */ auto lambda = make_lambda("lm_1", - {"x"}, + {x_var}, call2); log && log(xtag("expr", lambda)); From 2a4b9a4360a1806eee1bc85653ae9807fb8effc1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 10:54:42 -0400 Subject: [PATCH 1122/2524] xo-jit: + td_to_llvm_type() + support native int+float values --- include/xo/jit/MachPipeline.hpp | 2 +- src/jit/MachPipeline.cpp | 146 +++++++++++++++++++++++--------- 2 files changed, 108 insertions(+), 40 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 84b01a9d..3f217db3 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -96,7 +96,7 @@ namespace xo { void dump_current_module(); /** lookup symbol in jit-associated output library **/ - llvm::orc::ExecutorAddr lookup_symbol(const std::string & x); + llvm::Expected lookup_symbol(const std::string & x); virtual void display(std::ostream & os) const; virtual std::string display_string() const; diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 3192e531..e1851474 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -6,11 +6,13 @@ namespace xo { using xo::ast::exprtype; using xo::ast::Expression; using xo::ast::ConstantInterface; + using xo::ast::FunctionInterface; using xo::ast::PrimitiveInterface; using xo::ast::Lambda; using xo::ast::Variable; using xo::ast::Apply; using xo::ast::IfExpr; + using xo::reflect::Reflect; using xo::reflect::TypeDescr; using std::cerr; using std::endl; @@ -120,17 +122,51 @@ namespace xo { { TypeDescr td = expr->value_td(); - if (td->is_native()) { + if (Reflect::is_native(td)) { return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), llvm::APFloat(*(expr->value_tp().recover_native()))); - } else if (td->is_native()) { + } else if (Reflect::is_native(td)) { return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); } return nullptr; } + namespace { + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + if (Reflect::is_native(td)) { + return llvm::Type::getInt1Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt8Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt16Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt32Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt64Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getFloatTy(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getDoubleTy(llvm_cx_ref); + } else { + cerr << "td_to_llvm_type: no llvm type available for T" + << xtag("T", td->short_name()) + << endl; + return nullptr; + } + } + } + llvm::Function * MachPipeline::codegen_primitive(ref::brw expr) { @@ -158,7 +194,7 @@ namespace xo { // PLACEHOLDER // just make prototype for function :: double^n -> double - TypeDescr fn_td = expr->value_td(); + TypeDescr fn_td = expr->valuetype(); int n_fn_arg = fn_td->n_fn_arg(); scope log(XO_DEBUG(c_debug_flag), @@ -174,18 +210,12 @@ namespace xo { log && log(xtag("i_arg", i), xtag("arg_td", arg_td->short_name())); - if (arg_td->is_native()) { - llvm_argtype_v.push_back(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref())); + llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx_.borrow(), arg_td); - // TODO: extend with other native types here... - } else { - cerr << "MachPipeline::codegen_primitive: error: primitive f with arg i of type T where double expected" - << xtag("f", expr->name()) - << xtag("i", i) - << xtag("T", arg_td->short_name()) - << endl; + if (!llvm_argtype) return nullptr; - } + + llvm_argtype_v.push_back(llvm_argtype); } //std::vector double_v(n_fn_arg, llvm::Type::getDoubleTy(*llvm_cx_)); @@ -194,17 +224,10 @@ namespace xo { log && log(xtag("retval_td", retval_td->short_name())); - llvm::Type * llvm_retval = nullptr; + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), retval_td); - if (retval_td->is_native()) { - llvm_retval = llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()); - } else { - cerr << "MachPipeline::codegen_primitive: error: primitive f returning T where double expected" - << xtag("f", expr->name()) - << xtag("T", retval_td->short_name()) - << endl; + if (!llvm_retval) return nullptr; - } auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, llvm_argtype_v, @@ -247,9 +270,30 @@ namespace xo { * * For now, finesse by only handling PrimitiveInterface in function-callee position */ - if (apply->fn()->extype() == exprtype::primitive) { - auto pm = PrimitiveInterface::from(apply->fn()); - auto * fn = this->codegen_primitive(pm); + if (apply->fn()->extype() == exprtype::primitive + || apply->fn()->extype() == exprtype::lambda) + { + llvm::Function * llvm_fn = nullptr; + FunctionInterface * fn = nullptr; + { + // TODO: codgen_function() + + auto pm = PrimitiveInterface::from(apply->fn()); + if (pm) { + fn = pm.get(); + llvm_fn = this->codegen_primitive(pm); + } + + auto lm = Lambda::from(apply->fn()); + if (lm) { + fn = lm.get(); + llvm_fn = this->codegen_lambda(lm); + } + } + + if (!llvm_fn) { + return nullptr; + } #ifdef NOT_USING_DEBUG cerr << "MachPipeline::codegen_apply: fn:" << endl; @@ -257,15 +301,30 @@ namespace xo { cerr << endl; #endif - if (fn->arg_size() != apply->argv().size()) { + if (llvm_fn->arg_size() != apply->argv().size()) { cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" - << xtag("f", pm->name()) - << xtag("n1", pm->n_arg()) + << xtag("f", fn->name()) + << xtag("n1", fn->n_arg()) << xtag("n2", apply->argv().size()) << endl; + return nullptr; } + /** also check argument types **/ + for (size_t i = 0, n = fn->n_arg(); i < n; ++i) { + if (apply->argv()[i]->valuetype() != fn->fn_arg(i)) { + cerr << "MachPipeline::codegen_apply: error: callee f for arg i seeeing U instead of expected T" + << xtag("f", fn->name()) + << xtag("i", i) + << xtag("U", apply->argv()[i]->valuetype()->short_name()) + << xtag("T", fn->fn_arg(i)->short_name()) + << endl; + + return nullptr; + } + } + std::vector args; args.reserve(apply->argv().size()); @@ -281,7 +340,7 @@ namespace xo { args.push_back(arg); } - return llvm_ir_builder_->CreateCall(fn, args, "calltmp"); + return llvm_ir_builder_->CreateCall(llvm_fn, args, "calltmp"); } else { cerr << "MachPipeline::codegen_apply: error: only allowing call to known primitives at present" << endl; return nullptr; @@ -318,11 +377,16 @@ namespace xo { // PLACEHOLDER // just handle double arguments + return type for now - std::vector double_v(lambda->n_arg(), - llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref())); + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), lambda->fn_retval()); - auto * llvm_fn_type = llvm::FunctionType::get(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), - double_v, + std::vector arg_type_v(lambda->n_arg()); + + for (size_t i = 0, n = lambda->n_arg(); i < n; ++i) { + arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), lambda->fn_arg(i)); + } + + auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, + arg_type_v, false /*!varargs*/); /* create (initially empty) function */ @@ -336,7 +400,7 @@ namespace xo { for (auto & arg : fn->args()) { log && log("llvm format param names", xtag("i", i), xtag("param", lambda->argv().at(i))); - arg.setName(lambda->argv().at(i)); + arg.setName(lambda->argv().at(i)->name()); ++i; } } @@ -464,6 +528,7 @@ namespace xo { parent_fn->insert(parent_fn->end(), merge_bb); llvm_ir_builder_->SetInsertPoint(merge_bb); + /** TODO: switch to getInt1Ty here **/ llvm::PHINode * phi_node = llvm_ir_builder_->CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), 2 /*#of branches being merged (?)*/, @@ -533,18 +598,21 @@ namespace xo { this->recreate_llvm_ir_pipeline(); } - llvm::orc::ExecutorAddr + llvm::Expected MachPipeline::lookup_symbol(const std::string & sym) { static llvm::ExitOnError llvm_exit_on_err; /* llvm_sym: ExecutorSymbolDef */ - auto llvm_sym = llvm_exit_on_err(this->jit_->lookup(sym)); + auto llvm_sym_expected = this->jit_->lookup(sym); - /* llvm_addr: ExecutorAddr */ - auto llvm_addr = llvm_sym.getAddress(); + if (llvm_sym_expected) { + auto llvm_addr = llvm_sym_expected.get().getAddress(); - return llvm_addr; + return llvm_addr; + } else { + return llvm_sym_expected.takeError(); + } } /*lookup_symbol*/ void From af8b0d4a4675a708678a2c594027b2452b0955b7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 10:55:57 -0400 Subject: [PATCH 1123/2524] xo-pyreflect: build: streamline cmake --- CMakeLists.txt | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d84942e..427a56b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,22 +5,10 @@ cmake_minimum_required(VERSION 3.10) project(xo_pyreflect VERSION 0.1) enable_language(CXX) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.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/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings (usually temporary) @@ -28,8 +16,6 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* set(PROJECT_CXX_FLAGS "") add_definitions(${PROJECT_CXX_FLAGS}) -xo_toplevel_compile_options() - # ---------------------------------------------------------------- # sources From 00025a66e43416b907dc66c6de0a4c2af4e5ba55 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 10:56:29 -0400 Subject: [PATCH 1124/2524] xo-pyreflect: + TypeDescr.lookup_by_name() --- src/pyreflect/pyreflect.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index fc6e8fe8..f93b4634 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -28,6 +28,8 @@ namespace xo { //py::class_(m, "TypeDescr"); py::class_>(m, "TypeDescr") + + .def_static("lookup_by_name", &TypeDescrBase::lookup_by_name) .def_static("print_reflected_types", [](){ TypeDescrBase::print_reflected_types(std::cout); }) .def_property_readonly("canonical_name", &TypeDescrBase::canonical_name) From 50d3d32fc3a5900c3c8b516e6a38bb3d2b99f940 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 10:56:53 -0400 Subject: [PATCH 1125/2524] xo-pyreflect: + TypeDescr.short_name --- src/pyreflect/pyreflect.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index f93b4634..9447a4cd 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -33,6 +33,7 @@ namespace xo { .def_static("print_reflected_types", [](){ TypeDescrBase::print_reflected_types(std::cout); }) .def_property_readonly("canonical_name", &TypeDescrBase::canonical_name) + .def_property_readonly("short_name", &TypeDescrBase::short_name) .def("__repr__", &TypeDescrBase::display_string); /* note: this means python will use From e57ba44ae72b4f2218b1a6fad249479bc5cca521 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 10:57:14 -0400 Subject: [PATCH 1126/2524] xo-pyreflect: + TypeDescr.complete_flag --- src/pyreflect/pyreflect.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index 9447a4cd..cf65d3d1 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -34,6 +34,7 @@ namespace xo { [](){ TypeDescrBase::print_reflected_types(std::cout); }) .def_property_readonly("canonical_name", &TypeDescrBase::canonical_name) .def_property_readonly("short_name", &TypeDescrBase::short_name) + .def_property_readonly("complete_flag", &TypeDescrBase::complete_flag) .def("__repr__", &TypeDescrBase::display_string); /* note: this means python will use From bd8c2c4e13fa96efd0bfe6a826b42390c0c19d34 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 10:57:40 -0400 Subject: [PATCH 1127/2524] xo-pyreflect: bind Metatype; + TypeDescr.metatype --- src/pyreflect/pyreflect.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index cf65d3d1..a77993a5 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -22,6 +22,17 @@ namespace xo { namespace reflect { PYBIND11_MODULE(PYREFLECT_MODULE_NAME(), m) { + m.doc() = "pybind11 plugin for xo-reflect"; + + py::enum_(m, "Metatype") + .value("invalid", Metatype::mt_invalid) + .value("atomic", Metatype::mt_atomic) + .value("pointer", Metatype::mt_pointer) + .value("vector", Metatype::mt_vector) + .value("struct", Metatype::mt_struct) + .value("function", Metatype::mt_function) + ; + /* note: possibly move this to pytime/ if/when we provide it */ //py::class_(m, "utc_nanos"); @@ -32,8 +43,10 @@ namespace xo { .def_static("lookup_by_name", &TypeDescrBase::lookup_by_name) .def_static("print_reflected_types", [](){ TypeDescrBase::print_reflected_types(std::cout); }) + .def_property_readonly("canonical_name", &TypeDescrBase::canonical_name) .def_property_readonly("short_name", &TypeDescrBase::short_name) + .def_property_readonly("metatype", &TypeDescrBase::metatype) .def_property_readonly("complete_flag", &TypeDescrBase::complete_flag) .def("__repr__", &TypeDescrBase::display_string); From 76f5e4cacabc26ef72d652f0735cb0a3f1f43904 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 10:58:26 -0400 Subject: [PATCH 1128/2524] xo-preflect: ++ .gitignore (+ .projectile file) --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 53a9c92f..2d66a655 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# emacs workspace config +.projectile # lsp keeps state here .cache # typical build directory From dc4f13815a19b386cee6f9cd7b9094e74bee83f5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 11:01:24 -0400 Subject: [PATCH 1129/2524] xo-pyexpression: + Expression.valuetype --- src/pyexpression/pyexpression.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index 283df9ac..a69828d1 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -55,6 +55,7 @@ namespace xo { py::class_>(m, "Expression") .def_property_readonly("extype", &Expression::extype) + .def_property_readonly("valuetype", &Expression::valuetype) .def("__repr__", &Expression::display_string); ; @@ -106,7 +107,8 @@ namespace xo { m.def("make_sqrt_pm", []() { return make_primitive("sqrt", sqrt); }, py::doc("create primitive representing the ::sqrt() function")); - m.def("make_sin_pm", []() { return make_primitive("sin", ::sin); }, + m.def("make_sin_pm", + []() { return make_primitive("sin", ::sin); }, py::doc("create primitive representing the ::sin() function")); m.def("make_cos_pm", []() { return make_primitive("cos", ::cos); }, py::doc("create primitive representing the ::cos() function")); From 4132c7fba3778d8391fb509d3640249f3fa9b820 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 11:02:18 -0400 Subject: [PATCH 1130/2524] xo-pyjit: except (instead of abort) on failed prototype lookup --- src/pyjit/pyjit.cpp | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 1b53fdfb..1a3903c4 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -112,9 +112,9 @@ namespace xo { m.doc() = "pybind11 plugin for xo-jit"; pycaller_store::instance() - ->require_prototype(m, "double(double)"); + ->require_prototype(m, "double (*)(double)"); pycaller_store::instance() - ->require_prototype(m, "double(double,double)"); + ->require_prototype(m, "double (*)(double,double)"); //pycaller::declare_once(m); //pycaller::declare_once(m); @@ -179,28 +179,33 @@ namespace xo { * so any function type will appear to succeed here. * We cast to particular function type within the pycaller<..> template */ - auto fn_addr = llvm_addr.toPtr(); + if (llvm_addr) { + auto fn_addr = llvm_addr.get().toPtr(); - /* note: llvm_addr.toPtr<..> always succeeds, - * event if pointer refers to an object of incompatible type - * - * note: return value policy is for python to own the wrapper - * - * note: pycaller signatures need to have been introduced in advance - * (in practice determined at compile time, - * since they encode a function-signature-specific calling sequence) - * by calling pycaller_store::instance()->require_prototype(prototype); - */ + /* note: llvm_addr.toPtr<..> always succeeds, + * event if pointer refers to an object of incompatible type + * + * note: return value policy is for python to own the wrapper + * + * note: pycaller signatures need to have been introduced in advance + * (in practice determined at compile time, + * since they encode a function-signature-specific calling sequence) + * by calling pycaller_store::instance()->require_prototype(prototype); + */ - auto factory = pycaller_store::instance()->lookup_prototype(prototype); + auto factory = pycaller_store::instance()->lookup_prototype(prototype); - if (!factory) { - throw std::runtime_error(tostr("MachPipeline.lookup_fn: unknown function prototype", - xtag("p", prototype))); + if (!factory) { + throw std::runtime_error(tostr("MachPipeline.lookup_fn: unknown function prototype", + xtag("p", prototype))); + } + + return (*factory)(fn_addr); + } else { + throw std::runtime_error(tostr("MachPipeline.lookup_fn: lookup on symbol S failed", + xtag("S", symbol))); } - return (*factory)(fn_addr); - #ifdef OBSOLETE if((prototype == "double(double,double)") || (prototype == "double(*)(double,double)")) { return new pycaller(fn_addr); From 6edb6abd44ede4249670e7fdcff1ad0eb6b036ff Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 11:03:21 -0400 Subject: [PATCH 1131/2524] xo-pyjit: doc: update README example --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6119b705..a749ee43 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ Assumes `xo-pyjit` installed to `~/local2/lib`, i.e. built with `PREFIX=~/local2`. ``` PYTHONPATH=~/local2/lib:$PYTHONPATH python +>>> from xo_pyreflect import * >>> from xo_pyjit import * >>> from xo_pyexpression import * ``` @@ -71,12 +72,13 @@ Symbol table: build an AST from within python ``` ->>> x=make_var('x') # "x" a variable (context not yet known) +>>> f64_t=TypeDescr.lookup_by_name('double') +>>> x=make_var('x',f64_t) # "x" a variable (context not yet known) >>> f1=make_sin_pm() # "sin()" >>> c1=make_apply(f1,[x]) # "sin(x)" >>> f2=make_cos_pm() # "cos()" >>> c2=make_apply(f2,[c1]) # "cos(sin(x))" ->>> lm=make_lambda('foo', ['x'], c2) # "def foo(x): cos(sin(x))" +>>> lm=make_lambda('foo', [x], c2) # "def foo(x): cos(sin(x))" >>> lm :argv "[ :argv \"[]\">]">> ``` From 37b2bc85a41fac6ff5cdeb0d9fb397d43a9391ae Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:14:02 -0400 Subject: [PATCH 1132/2524] xo-expression: + explicit_symbol_def feature in PrimitiveInterface --- example/ex1/ex1.cpp | 4 ++- include/xo/expression/Primitive.hpp | 26 +++++++++++++++----- include/xo/expression/PrimitiveInterface.hpp | 16 ++++++++++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 5c12a4b1..be606918 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -17,7 +17,9 @@ main() { } { - auto expr = make_primitive("sqrt", &::sqrt); + auto expr = make_primitive("sqrt", + &::sqrt, + false /*!explicit_symbol_def*/); auto expr_td = expr->value_td(); diff --git a/include/xo/expression/Primitive.hpp b/include/xo/expression/Primitive.hpp index f251465d..caf7f1bf 100644 --- a/include/xo/expression/Primitive.hpp +++ b/include/xo/expression/Primitive.hpp @@ -35,10 +35,11 @@ namespace xo { public: static ref::rp make(const std::string & name, - FunctionPointer fnptr) { + FunctionPointer fnptr, + bool explicit_symbol_def) { TypeDescr fn_type = Reflect::require(); - return new Primitive(fn_type, name, fnptr); + return new Primitive(fn_type, name, fnptr, explicit_symbol_def); } FunctionPointer value() const { return value_; } @@ -54,6 +55,9 @@ namespace xo { // ----- PrimitiveInterface ----- + virtual bool explicit_symbol_def() const override { return explicit_symbol_def_; } + virtual void_function_type function_address() const override { return reinterpret_cast(value_); } + // ----- FunctionInterface ----- virtual std::string const & name() const override { return name_; } @@ -74,11 +78,13 @@ namespace xo { private: Primitive(TypeDescr fn_type, const std::string & name, - FunctionPointer fnptr) + FunctionPointer fnptr, + bool explicit_symbol_def) : PrimitiveInterface(fn_type), name_{name}, value_td_{Reflect::require_function()}, - value_{fnptr} + value_{fnptr}, + explicit_symbol_def_{explicit_symbol_def} { if (!value_td_->is_function()) throw std::runtime_error("Primitive: expected function pointer"); @@ -97,13 +103,21 @@ namespace xo { TypeDescr value_td_; /** address of executable function **/ FunctionPointer value_; + /** if true, use Jit.intern_symbol() to provide explicit binding. + * Currently mystified as to what's distinguishes functions like ::sin(), ::sqrt() + * (which work do not require this) from symbols like ::mul_i32(), which do) + **/ + bool explicit_symbol_def_ = false; }; /*Primitive*/ /** adopt function @p x as a callable primitive function named @p name **/ template ref::rp> - make_primitive(const std::string & name, FunctionPointer x) { - return Primitive::make(name, x); + make_primitive(const std::string & name, + FunctionPointer x, + bool explicit_symbol_def) + { + return Primitive::make(name, x, explicit_symbol_def); } } /*namespace ast*/ } /*namespace xo*/ diff --git a/include/xo/expression/PrimitiveInterface.hpp b/include/xo/expression/PrimitiveInterface.hpp index 685d04ae..0b42b252 100644 --- a/include/xo/expression/PrimitiveInterface.hpp +++ b/include/xo/expression/PrimitiveInterface.hpp @@ -13,6 +13,9 @@ namespace xo { namespace ast { class PrimitiveInterface : public FunctionInterface { + public: + using void_function_type = void (*)(); + public: PrimitiveInterface(TypeDescr fn_type) : FunctionInterface(exprtype::primitive, fn_type) {} @@ -22,6 +25,19 @@ namespace xo { return ref::brw::from(x); } + /** if true, Jit will try to explicitly symbol for this primitive + * (instead of looking it up in host process). + * Don't know if this works. + * Do know it's not needed for ::sin(), ::sqrt(). + * Do know that my extern "C" functions like ::mul_i32(), ::mul_f64() + * need something else. + **/ + virtual bool explicit_symbol_def() const = 0; + + /** function address for this primitive + **/ + virtual void_function_type function_address() const = 0; + // virtual const std::string & name() const; // virtual int n_arg() const; // virtual TypeDescr fn_retval() const; From 34123662b8d33735318e987fb8aa0f60aec86388 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:16:47 -0400 Subject: [PATCH 1133/2524] xo-pyexpression: supply explicit_symbol_def to make_primitive --- src/pyexpression/pyexpression.cpp | 38 +++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index a69828d1..10307d68 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -103,27 +103,45 @@ namespace xo { py::doc("number of arguments to this function (not counting return value)")) ; - using Fn_dbl_dbl_type = double (*)(double); + using int32_t = std::int32_t; - m.def("make_sqrt_pm", []() { return make_primitive("sqrt", sqrt); }, - py::doc("create primitive representing the ::sqrt() function")); - m.def("make_sin_pm", - []() { return make_primitive("sin", ::sin); }, - py::doc("create primitive representing the ::sin() function")); - m.def("make_cos_pm", []() { return make_primitive("cos", ::cos); }, - py::doc("create primitive representing the ::cos() function")); - m.def("make_pow_pm", []() { return make_primitive("pow", ::pow); }, - py::doc("create primitive representing the ::pow() function")); + py::class_, + PrimitiveInterface, + rp>>(m, "Primitive_i32_i32") + ; py::class_, PrimitiveInterface, rp>>(m, "Primitive_double_double") ; + using Fn_dbl_dbl_type = double (*)(double); + + m.def("make_sqrt_pm", []() { return make_primitive("sqrt", + ::sqrt, + false /*!explicit_symbol_def*/); }, + py::doc("create primitive representing the ::sqrt() function")); + m.def("make_sin_pm", + []() { return make_primitive("sin", + ::sin, + false /*!explicit_symbol_def*/); }, + py::doc("create primitive representing the ::sin() function")); + m.def("make_cos_pm", + []() { return make_primitive("cos", + ::cos, + false /*!explicit_symbol_def*/); }, + py::doc("create primitive representing the ::cos() function")); + py::class_, PrimitiveInterface, rp>>(m, "Primitive_double_double_double") ; + m.def("make_pow_pm", + []() { return make_primitive("pow", + ::pow, + false /*!explicit_symbol_def*/); }, + py::doc("create primitive representing the ::pow() function")); + // ----- Apply ----- py::class_>(m, "Apply") From 1f02ec02effd0ad69fee0b1fdc9d22e372122928 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:18:15 -0400 Subject: [PATCH 1134/2524] xo-pyexpression: experiment: try moving xo intrinsics into xo-jit --- include/xo/jit/intrinsics.hpp | 13 +++++++++++++ src/jit/intrinsics.cpp | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 include/xo/jit/intrinsics.hpp create mode 100644 src/jit/intrinsics.cpp diff --git a/include/xo/jit/intrinsics.hpp b/include/xo/jit/intrinsics.hpp new file mode 100644 index 00000000..a0528dc2 --- /dev/null +++ b/include/xo/jit/intrinsics.hpp @@ -0,0 +1,13 @@ +/** @file intrinsics.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include + +extern "C" int32_t mul_i32(int32_t x, int32_t y); +extern "C" double mul_f64(double x, double y); + +/** end intrinsics.hpp **/ diff --git a/src/jit/intrinsics.cpp b/src/jit/intrinsics.cpp new file mode 100644 index 00000000..2abdf7d8 --- /dev/null +++ b/src/jit/intrinsics.cpp @@ -0,0 +1,20 @@ +/* @file intrinsics.cpp */ + +#include "intrinsics.hpp" + +/* FIXME: don't know how to mangle symbols yet, + * so putting functions invoked from jit into global namespace + */ +extern "C" +int32_t +mul_i32(int32_t x, int32_t y) { + return x * y; +} + +extern "C" +double +mul_f64(double x, double y) { + return x * y; +} + +/* end intrinsics.cpp */ From d59328ee1f99a6d699c94bc069e71463f24a1ca8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:19:02 -0400 Subject: [PATCH 1135/2524] xo-jit: build: + intrinsics --- src/jit/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index 88b9d2da..aa0a3deb 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -5,6 +5,7 @@ set(SELF_SRCS LlvmContext.cpp IrPipeline.cpp MachPipeline.cpp + intrinsics.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) From c9a061abf0a283123046051e627afa72769d1b31 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:19:25 -0400 Subject: [PATCH 1136/2524] xo-jit: supply explicit_symbol_def to make_primitive() --- example/ex1/ex1.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 3929bcfa..6f5a7c35 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -81,7 +81,8 @@ main() { } { - auto expr = make_primitive("sqrt", &sqrt); + auto expr = make_primitive("sqrt", &sqrt, + false /*!explicit_symbol_def*/); log && log(xtag("expr", expr)); @@ -102,7 +103,8 @@ main() { { /* (sqrt 2) */ - auto fn = make_primitive("sqrt", &sqrt); + auto fn = make_primitive("sqrt", &sqrt, + false /*!explicit_symbol_def*/); auto arg = make_constant(2.0); auto call = make_apply(fn, {arg}); @@ -126,8 +128,10 @@ main() { { /* (lambda (x) (sin (cos x))) */ - auto sin = make_primitive("sin", ::sin); - auto cos = make_primitive("cos", ::cos); + auto sin = make_primitive("sin", ::sin, + false /*!explicit_symbol_def*/); + auto cos = make_primitive("cos", ::cos, + false /*!explicit_symbol_def*/); auto x_var = make_var("x", Reflect::require()); auto call1 = make_apply(cos, {x_var}); /* (cos x) */ From 2e99cc7c31025aa74456d9570b56731b0546b8c3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:19:59 -0400 Subject: [PATCH 1137/2524] xo-jit: minor: fix convention (stray _ prefix on format param) --- include/xo/jit/Jit.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index feb78652..1847084a 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -62,9 +62,9 @@ namespace xo { public: Jit(std::unique_ptr xsession, JITTargetMachineBuilder jtmb, - DataLayout data_layout_) + DataLayout data_layout) : xsession_{std::move(xsession)}, - data_layout_(std::move(data_layout_)), + data_layout_(std::move(data_layout)), mangler_(*this->xsession_, this->data_layout_), object_layer_(*this->xsession_, []() { return std::make_unique(); }), From 97aea9d513cecd801b8c620c66e65220dbaaef88 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:20:53 -0400 Subject: [PATCH 1138/2524] xo-jit: + Jit::mangle() --- include/xo/jit/Jit.hpp | 7 ++++++- include/xo/jit/MachPipeline.hpp | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 1847084a..6a160323 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -126,9 +126,14 @@ namespace xo { std::move(ts_module)); } + /** report mangled symbol name **/ + auto mangle(StringRef name) { + return this->mangler_(name.str()); + } + llvm::Expected lookup(StringRef name) { return this->xsession_->lookup({&dest_dynamic_lib_}, - this->mangler_(name.str())); + this->mangle(name)); } /* dump */ diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 3f217db3..967e3127 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -95,6 +95,9 @@ namespace xo { /** dump text description of module contents to console **/ void dump_current_module(); + /** report mangle symbol **/ + std::string mangle(const std::string & x) const; + /** lookup symbol in jit-associated output library **/ llvm::Expected lookup_symbol(const std::string & x); From 3f441d8ba6fa4f5ded22b435d204912aa465390d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:21:18 -0400 Subject: [PATCH 1139/2524] xo-jit: cosmetic: code layout/comments --- src/jit/MachPipeline.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index e1851474..b97f9129 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -137,7 +137,7 @@ namespace xo { } return nullptr; - } + } /*codegen_constant*/ namespace { llvm::Type * @@ -204,11 +204,12 @@ namespace xo { std::vector llvm_argtype_v; llvm_argtype_v.reserve(n_fn_arg); - /** check function args are all doubles **/ + /** check function args are all known **/ for (int i = 0; i < n_fn_arg; ++i) { TypeDescr arg_td = fn_td->fn_arg(i); - log && log(xtag("i_arg", i), xtag("arg_td", arg_td->short_name())); + log && log(xtag("i_arg", i), + xtag("arg_td", arg_td->short_name())); llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx_.borrow(), arg_td); From 787d0b69e2338c4b8468cba4c52781b1e9989e04 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:21:46 -0400 Subject: [PATCH 1140/2524] xo-jit: + MachPipeline::mangle() --- src/jit/MachPipeline.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index b97f9129..34578cf4 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -598,6 +598,18 @@ namespace xo { this->recreate_llvm_ir_pipeline(); } + std::string + MachPipeline::mangle(const std::string & sym) const + { + auto p = this->jit_->mangle(sym); + + if (p) + return (*p).str(); + + throw std::runtime_error(tostr("MachPipeline::mangle" + ": mangle(sym) returned empty pointer", + xtag("sym", sym))); + } /*mangle*/ llvm::Expected MachPipeline::lookup_symbol(const std::string & sym) From 69ee09fa59222a960b3ee18c21198288e42a15f8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:22:08 -0400 Subject: [PATCH 1141/2524] xo-jit: tidy: drop unused temporary --- src/jit/MachPipeline.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 34578cf4..de881aca 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -614,8 +614,6 @@ namespace xo { llvm::Expected MachPipeline::lookup_symbol(const std::string & sym) { - static llvm::ExitOnError llvm_exit_on_err; - /* llvm_sym: ExecutorSymbolDef */ auto llvm_sym_expected = this->jit_->lookup(sym); From 39a8e8aad4078ea7d5f011e870d91b010de801ef Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:22:24 -0400 Subject: [PATCH 1142/2524] xo-jit: cosmetic: code layout / comments --- include/xo/jit/Jit.hpp | 2 +- src/jit/MachPipeline.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 6a160323..8cf15af6 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -142,7 +142,7 @@ namespace xo { } }; /*Jit*/ - } /*namespace jit&*/ + } /*namespace jit*/ } /*namespace xo*/ /** end Jit.hpp **/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index de881aca..d72de5a1 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -465,8 +465,7 @@ namespace xo { { llvm::Value * test_ir = this->codegen(expr->test()); - /** need test result in a variable - **/ + /** need test result in a variable **/ llvm::Value * test_with_cmp_ir = llvm_ir_builder_->CreateFCmpONE(test_ir, llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), @@ -538,7 +537,7 @@ namespace xo { phi_node->addIncoming(when_false_ir, when_false_bb); return phi_node; - } + } /*codegen_ifexpr*/ llvm::Value * MachPipeline::codegen(ref::brw expr) @@ -597,7 +596,8 @@ namespace xo { llvm_exit_on_err(this->jit_->add_llvm_module(std::move(ts_module), tracker)); this->recreate_llvm_ir_pipeline(); - } + } /*machgen_current_module*/ + std::string MachPipeline::mangle(const std::string & sym) const { From ba39b6366d2dc6e00adba98bf93ad6156c711c4f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:22:53 -0400 Subject: [PATCH 1143/2524] xo-jit: + Jit::intern_symbol() --- include/xo/jit/Jit.hpp | 13 +++++++++++++ src/jit/MachPipeline.cpp | 1 + 2 files changed, 14 insertions(+) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 8cf15af6..435a4bc6 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -126,6 +126,19 @@ namespace xo { std::move(ts_module)); } + /** intern @p symbol, binding it to address @p dest **/ + template + llvm::Error intern_symbol(const std::string & symbol, T * dest) { + llvm::orc::SymbolMap symbol_map; + symbol_map[mangler_(symbol)] + = llvm::orc::ExecutorSymbolDef(llvm::orc::ExecutorAddr::fromPtr(dest), + llvm::JITSymbolFlags()); + + auto materializer = llvm::orc::absoluteSymbols(symbol_map); + + return dest_dynamic_lib_.define(materializer); + } /*intern_symbol*/ + /** report mangled symbol name **/ auto mangle(StringRef name) { return this->mangler_(name.str()); diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index d72de5a1..162c113d 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -1,6 +1,7 @@ /* @file MachPipeline.cpp */ #include "MachPipeline.hpp" +#include namespace xo { using xo::ast::exprtype; From c09f1f46df17346e0aed45240e20e94724cc47b8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:23:23 -0400 Subject: [PATCH 1144/2524] xo-jit: in codegen_primitive() honor explicit_symbol_def flag --- src/jit/MachPipeline.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 162c113d..0baf2c3e 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -254,6 +254,25 @@ namespace xo { } #endif + if (expr->explicit_symbol_def()) { + static llvm::ExitOnError llvm_exit_on_err; + + llvm_exit_on_err(this->jit_->intern_symbol(expr->name(), + expr->function_address())); + +#ifdef NOT_USING + if (!llvm_result) { + cerr << "MachPipeline::codegen_primitive" + << ": intern_symbol failed" + << xtag("name", expr->name()) + << xtag("addr", expr->function_address()) + << endl; + + return nullptr; + } +#endif + } + log && log("returning llvm function"); return fn; From 0273a8b8dfe8795f4976055cd27f42d36dbba518 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:24:06 -0400 Subject: [PATCH 1145/2524] xo-jit: cosmetic: code layout --- include/xo/jit/Jit.hpp | 7 ++++--- src/jit/Jit.cpp | 10 ++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 src/jit/Jit.cpp diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 435a4bc6..5af092d0 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -72,9 +72,10 @@ namespace xo { std::make_unique(std::move(jtmb))), dest_dynamic_lib_(this->xsession_->createBareJITDylib("
")) { - dest_dynamic_lib_.addGenerator( - cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess( - data_layout_.getGlobalPrefix()))); + dest_dynamic_lib_.addGenerator + (cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess + (data_layout_.getGlobalPrefix()))); + if (jtmb.getTargetTriple().isOSBinFormatCOFF()) { object_layer_.setOverrideObjectFlagsWithResponsibilityFlags(true); object_layer_.setAutoClaimResponsibilityForObjectSymbols(true); diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp new file mode 100644 index 00000000..8f915406 --- /dev/null +++ b/src/jit/Jit.cpp @@ -0,0 +1,10 @@ +/* @file Jit.cpp */ + +#include "Jit.hpp" + +namespace xo { + namespace jit { + } /*namespace jit*/ +} /*namespace xo*/ + +/* end Jit.cpp */ From faed5f59c6bafbeea5e46feb2ee97484bdc1b9ae Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:29:15 -0400 Subject: [PATCH 1146/2524] xo-pyjit: remove OBSOLETE code --- src/pyjit/pyjit.cpp | 54 --------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 1a3903c4..c1706a2c 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -8,28 +8,6 @@ #include namespace xo { -#ifdef OBSOLETE - struct XferDbl2DblFn : public ref::Refcount { - using fptr_type = double (*) (double); - - explicit XferDbl2DblFn(fptr_type fptr) : fptr_{fptr} {} - - double operator() (double x) { return (*fptr_)(x); } - - fptr_type fptr_; - }; /*XferDbl2DblFn*/ - - struct XferDblDbl2DblFn : public ref::Refcount { - using fptr_type = double (*) (double, double); - - explicit XferDblDbl2DblFn(fptr_type fptr) : fptr_{fptr} {} - - double operator() (double x, double y) { return (*fptr_)(x, y); } - - fptr_type fptr_; - }; /*XferDblDbl2DblFn*/ -#endif - namespace jit { using xo::ast::Expression; using xo::pyutil::pycaller_base; @@ -149,27 +127,6 @@ namespace xo { .def("dump_current_module", &MachPipeline::dump_current_module, py::doc("Dump contents of current module to console")) -#ifdef OBSOLETE - /* double -> double */ - .def("lookup_dbl2dbl_fn", - [](MachPipeline & jit, const std::string & symbol) { - auto llvm_addr = jit.lookup_symbol(symbol); - - auto fn_addr = llvm_addr.toPtr(); - - return new XferDbl2DblFn(fn_addr); - }) - - /* (double x double) -> double */ - .def("lookup_dbldbl2dbl_fn", - [](MachPipeline & jit, const std::string & symbol) { - auto llvm_addr = jit.lookup_symbol(symbol); - - auto fn_addr = llvm_addr.toPtr(); - - return new XferDblDbl2DblFn(fn_addr); - }) -#endif .def("lookup_fn", [](MachPipeline & jit, const std::string & prototype, const std::string & symbol) -> pycaller_base* { @@ -205,17 +162,6 @@ namespace xo { throw std::runtime_error(tostr("MachPipeline.lookup_fn: lookup on symbol S failed", xtag("S", symbol))); } - -#ifdef OBSOLETE - if((prototype == "double(double,double)") || (prototype == "double(*)(double,double)")) { - return new pycaller(fn_addr); - } else if ((prototype == "double(double)") || (prototype == "double(*)(double)")) { - return new pycaller(fn_addr); - } else { - throw std::runtime_error(tostr("MachPipeline.lookup_fn: unknown function prototype", - xtag("p", prototype))); - } -#endif }) ; From 48efe6b31935cc75fdc6029e76ad358b2cafffe5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:29:43 -0400 Subject: [PATCH 1147/2524] xo-pyjit: + llvm_version() --- src/pyjit/pyjit.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index c1706a2c..69e4a4c3 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -5,6 +5,7 @@ #include "xo/jit/MachPipeline.hpp" #include "xo/pyutil/pycaller.hpp" #include "xo/pyutil/pyutil.hpp" +#include #include namespace xo { @@ -97,6 +98,9 @@ namespace xo { //pycaller::declare_once(m); //pycaller::declare_once(m); + m.def("llvm_version", []() { return LLVM_VERSION_STRING; }, + py::doc("llvm_version() reports compile-time llvm version string (via [llvm-config.h])")); + py::class_>(m, "MachPipeline") .def_static("make", &MachPipeline::make, py::doc("Create machine pipeline for in-process code generation" From a2cb8ae60fea391259d0b9530ddd3df658958c84 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:30:40 -0400 Subject: [PATCH 1148/2524] xo-pyutil: bugfix: + prototype_str for unique declare_once() syms --- include/xo/pyutil/pycaller.hpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/include/xo/pyutil/pycaller.hpp b/include/xo/pyutil/pycaller.hpp index 20ecc92c..a9715d2e 100644 --- a/include/xo/pyutil/pycaller.hpp +++ b/include/xo/pyutil/pycaller.hpp @@ -17,6 +17,7 @@ namespace xo { virtual ~pycaller_base() = default; + /* note: need inherited class pycaller_base revealed to pybind11 too */ static pybind11::module & declare_once(pybind11::module & m) { static bool s_once = false; if (!s_once) { @@ -27,8 +28,10 @@ namespace xo { } }; - /** Invoke function pointer of type Retval(*)(Args...), - * with arguments converted from py::object, and return type converted back to py::object. + /** @class pycaller + * @brief Invoke function pointer of type Retval(*)(Args...) from py::object + * + * Arguments converted from py::object, and return type converted back to py::object. * * Each distinct combination of {Retval,Args...} needs to be established at compile time * (since we need PyCall to be instantiated for particular types) @@ -49,12 +52,14 @@ namespace xo { static pycaller_base * make(void_function_type addr) { return new pycaller(addr); } - static pybind11::module & declare_once(pybind11::module & m) { + /* note: prototype_str must be [const char *], pybind11 requirement */ + static pybind11::module & declare_once(pybind11::module & m, + const char * prototype_str) { static bool s_once = false; if (!s_once) { s_once = true; pycaller_base::declare_once(m); - pybind11::class_(m, "pycaller0") + pybind11::class_(m, prototype_str) .def("__call__", [](self_type & self) { @@ -80,12 +85,14 @@ namespace xo { static pycaller_base * make(void_function_type addr) { return new pycaller(addr); } - static pybind11::module & declare_once(pybind11::module & m) { + /* note: prototype_str must be [const char *], pybind11 requirement */ + static pybind11::module & declare_once(pybind11::module & m, + const char * prototype_str) { static bool s_once = false; if (!s_once) { s_once = true; pycaller_base::declare_once(m); - pybind11::class_(m, "pycaller1") + pybind11::class_(m, prototype_str) .def("__call__", [](self_type & self, Arg1 arg1) { @@ -114,12 +121,14 @@ namespace xo { static pycaller_base * make(void_function_type addr) { return new pycaller(addr); } - static pybind11::module & declare_once(pybind11::module & m) { + /* note: prototype_str must be [const char *], pybind11 requirement */ + static pybind11::module & declare_once(pybind11::module & m, + const char * prototype_str) { static bool s_once = false; if (!s_once) { s_once = true; pycaller_base::declare_once(m); - pybind11::class_(m, "pycaller2") + pybind11::class_(m, prototype_str) .def("__call__", [](self_type & self, Arg1 arg1, Arg2 arg2) { From 7e5aca41e7e4f50b6c53df9f28a3852db622cdd3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:33:51 -0400 Subject: [PATCH 1149/2524] xo-pyjit: experiment: + install mul_i32/mul_f64 intrinsics here --- src/pyjit/pyjit.cpp | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 69e4a4c3..0409c016 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -3,6 +3,8 @@ #include "pyjit.hpp" #include "xo/pyexpression/pyexpression.hpp" #include "xo/jit/MachPipeline.hpp" +#include "xo/jit/intrinsics.hpp" +#include "xo/expression/Primitive.hpp" #include "xo/pyutil/pycaller.hpp" #include "xo/pyutil/pyutil.hpp" #include @@ -11,6 +13,7 @@ namespace xo { namespace jit { using xo::ast::Expression; + using xo::ast::make_primitive; using xo::pyutil::pycaller_base; using xo::pyutil::pycaller; using xo::ref::rp; @@ -32,15 +35,25 @@ namespace xo { * * Although it takes module as argument, the module being used * doesn't (shoudn't ??) matter + * + * note: pybind11 requires [const char *] pycaller_id_str + * + * Example: + * pycaller_store::instance() + * ->require_prototype*(m, "pycaller_i32_i32", "int (*)(int)") + * + * @p pycaller_id_str python pycaller class name; must be unique + * @p prototype_str prototype string for @ref lookup_prototype; must be unique **/ template pycaller_base::factory_function_type require_prototype(py::module & m, - const std::string & prototype_str) + const char * pycaller_id_str, + const char * prototype_str) { using caller_type = pycaller; - caller_type::declare_once(m); + caller_type::declare_once(m, pycaller_id_str); /* factory function takes function pointer of type * Retval(*)(Args...) @@ -90,10 +103,15 @@ namespace xo { m.doc() = "pybind11 plugin for xo-jit"; + /* reminder: prototype_str must be valid python class name */ pycaller_store::instance() - ->require_prototype(m, "double (*)(double)"); + ->require_prototype(m, "pycaller_i32_i32", "int (*)(int)"); pycaller_store::instance() - ->require_prototype(m, "double (*)(double,double)"); + ->require_prototype(m, "pycaller_i32_i32_i32", "int (*)(int, int)"); + pycaller_store::instance() + ->require_prototype(m, "pycaller_f64_f64", "double (*)(double)"); + pycaller_store::instance() + ->require_prototype(m, "pycaller_f64_f64_f64", "double (*)(double, double)"); //pycaller::declare_once(m); //pycaller::declare_once(m); @@ -101,6 +119,22 @@ namespace xo { m.def("llvm_version", []() { return LLVM_VERSION_STRING; }, py::doc("llvm_version() reports compile-time llvm version string (via [llvm-config.h])")); + m.def("make_mul_i32_pm", + []() + { + return make_primitive + ("mul_i32", ::mul_i32, true /*explicit_symbol_def*/); + }, + py::doc("create primitive for 32-bit signed integer multiplication")); + + m.def("make_mul_f64_pm", + []() + { + return make_primitive + ("mul_f64", ::mul_f64, true /*explicit_symbol_def*/); + }, + py::doc("create primitive for 64-bit floating point multiplication")); + py::class_>(m, "MachPipeline") .def_static("make", &MachPipeline::make, py::doc("Create machine pipeline for in-process code generation" From b7c4e8f93c2fc4b22185198c2197278ac385602d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:34:39 -0400 Subject: [PATCH 1150/2524] xo-pyjit: + MachPipeline::mangle --- src/pyjit/pyjit.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 0409c016..e905c43c 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -165,6 +165,10 @@ namespace xo { .def("dump_current_module", &MachPipeline::dump_current_module, py::doc("Dump contents of current module to console")) + .def("mangle", &MachPipeline::mangle, + py::arg("symbol"), + py::doc("mangle(symbol) reports mangled version of symbol.\n" + "throws exception if mangling fails")) .def("lookup_fn", [](MachPipeline & jit, const std::string & prototype, const std::string & symbol) -> pycaller_base* { From 8f57d15eac72fbb5f265efcd0050207d1b15c38b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:34:52 -0400 Subject: [PATCH 1151/2524] xo-pyjit: cosmetic: drop OBSOLETE code --- src/pyjit/pyjit.cpp | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index e905c43c..959f0931 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -195,7 +195,7 @@ namespace xo { auto factory = pycaller_store::instance()->lookup_prototype(prototype); if (!factory) { - throw std::runtime_error(tostr("MachPipeline.lookup_fn: unknown function prototype", + throw std::runtime_error(tostr("MachPipeline.lookup_fn: unknown function prototype p", xtag("p", prototype))); } @@ -204,23 +204,14 @@ namespace xo { throw std::runtime_error(tostr("MachPipeline.lookup_fn: lookup on symbol S failed", xtag("S", symbol))); } - }) + }, + py::arg("prototype"), py::arg("symbol"), + py::doc("lookup_fn(proto,sym) fetches function associated with sym in jit,\n" + "and wraps it as a callable python function.\n" + "proto *must* match (with exact spelling) pycaller registered at compile time with pycaller_store::instance,\n" + "for example 'int (*)(int, int)'")) ; - -#ifdef OBSOLETE - py::class_>(m, "XferDbl2DblFn") - .def("__call__", - [](XferDbl2DblFn & self, double x) { return self(x); } - ) - ; - py::class_>(m, "XferDblDbl2DblFn") - .def("__call__", - [](XferDblDbl2DblFn & self, double x, double y) { return self(x, y); } - ) - ; -#endif - py::class_>(m, "llvm_Value") .def("print", From fd033bdf60792748a62833bcdf33e50ed87c1c31 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 14:02:52 -0400 Subject: [PATCH 1152/2524] xo-cmake: + xo-ratio in xo-build --- bin/xo-build.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/xo-build.in b/bin/xo-build.in index dbbfc187..49bbf4d6 100644 --- a/bin/xo-build.in +++ b/bin/xo-build.in @@ -84,6 +84,7 @@ xo-cmake xo-indentlog xo-refcnt xo-subsys +xo-ratio xo-reflect xo-pyutil xo-pyreflect @@ -112,6 +113,9 @@ repo() { xo-subsys) echo "${XO_REPO_STEM}/subsys.git" ;; + xo-ratio) + echo "${XO_REPO_STEM}/xo-ratio.git" + ;; xo-reflect) echo "${XO_REPO_STEM}/reflect.git" ;; From c7c21969e889f9b15827cb8c4d2da818df8cb596 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 14:05:26 -0400 Subject: [PATCH 1153/2524] xo-expression: + llvmintrinsics enum --- include/xo/expression/llvmintrinsic.hpp | 92 +++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 include/xo/expression/llvmintrinsic.hpp diff --git a/include/xo/expression/llvmintrinsic.hpp b/include/xo/expression/llvmintrinsic.hpp new file mode 100644 index 00000000..37f231fe --- /dev/null +++ b/include/xo/expression/llvmintrinsic.hpp @@ -0,0 +1,92 @@ +/** @file llvmintrinsic.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +//#include + +namespace xo { + namespace ast { + /** @enum llvminstrinsic + * @brief enum to identify an LLVM instrinsic, e.g. @c IRBuilder::CreateFAdd + * + * Associate an @c llvminstrinsic with an AST @c Primitive p. + * Later, in @c xo::jit::IrPipeline + * - when generating code for @c xo::ast::Apply + * - with *p* is in the function-call position + * can use the associated llvm instrinsic instead of generating a function call + * @c Primitive::value + * + * @note llvm will still need to use @c Primitive::value, for example + * when handling an @c xo::ast::Apply instance where the function position + * is a computed function. + * @endnote + * + * @note + * NSW stands for 'no signed wrap' -> poison value if overflow (costs more) + * NUW stands for 'no unsigned wrap' -> poison value if overflow (costs more) + * @endnote + **/ + enum class llvmintrinsic { + /** sentinel value **/ + invalid = -1, + + /** -> IRBuilder::CreateAdd (add 2 integers, overflow silently) **/ + i_add, + + /** -> IRBuilder::CreateMul (multiply 2 integers, overflow silently) **/ + i_mul, + + /** -> IRBuilder::CreateFadd (add 2 floating-point numbers) **/ + fp_add, + + /** -> IRBuilder::CreateFmul (multiply 2 floating-point numbers) **/ + fp_mul, + + /** + * want to do whatever llvm IR @c llvm.sqrt.f64 and friends do. + * Not sure if that's an always-available function of something else + **/ + fp_sqrt, + + /** WIP **/ + fp_pow, + + /** WIP **/ + fp_sin, + + /** WIP **/ + fp_cos, + + /** WIP **/ + fp_tan, + + /** not an intrinsic. comes last, counts entries **/ + n_intrinsic + }; + + inline const char * + llvmintrinsic2str(llvmintrinsic x) + { + switch(x) { + case llvmintrinsic::invalid: return "?llvminstrinsic"; + case llvmintrinsic::i_add: return "i_add"; + case llvmintrinsic::i_mul: return "i_mul"; + case llvmintrinsic::fp_add: return "fp_add"; + case llvmintrinsic::fp_mul: return "fp_mul"; + case llvmintrinsic::fp_sqrt: return "fp_sqrt"; + case llvmintrinsic::fp_pow: return "fp_pow"; + case llvmintrinsic::fp_sin: return "fp_sin"; + case llvmintrinsic::fp_cos: return "fp_cos"; + case llvmintrinsic::fp_tan: return "fp_tan"; + default: break; + } + + return "???llvmintrinsic???"; + } /*llvmintrinsic2str*/ + } /*namespace ast*/ +} /*namespace xo*/ + +/** end llvmintrinsic.hpp **/ From 4d486c0e5cac8c5153674c9ceacd46084d096423 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 14:05:49 -0400 Subject: [PATCH 1154/2524] xo-expression: + Primitive::intrinsic --- example/ex1/ex1.cpp | 5 ++- include/xo/expression/Primitive.hpp | 37 +++++++++++++------- include/xo/expression/PrimitiveInterface.hpp | 7 ++-- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index be606918..5dd91fe2 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -2,6 +2,7 @@ #include "xo/expression/Constant.hpp" #include "xo/expression/Primitive.hpp" +#include "xo/expression/llvmintrinsic.hpp" #include #include @@ -9,6 +10,7 @@ int main() { using xo::ast::make_constant; using xo::ast::make_primitive; + using xo::ast::llvmintrinsic; using std::cout; using std::endl; @@ -19,7 +21,8 @@ main() { { auto expr = make_primitive("sqrt", &::sqrt, - false /*!explicit_symbol_def*/); + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sqrt); auto expr_td = expr->value_td(); diff --git a/include/xo/expression/Primitive.hpp b/include/xo/expression/Primitive.hpp index caf7f1bf..49246d8b 100644 --- a/include/xo/expression/Primitive.hpp +++ b/include/xo/expression/Primitive.hpp @@ -6,6 +6,7 @@ #pragma once #include "PrimitiveInterface.hpp" +#include "llvmintrinsic.hpp" #include "xo/reflect/Reflect.hpp" //#include @@ -15,9 +16,11 @@ namespace xo { * @brief syntax for a constant that refers to a known function. * * Two cases here: - * 1. primitive refers to a function that is supported directly in llvm - * (e.g. floating-point addition) - * 2. primitive refers to a compiled (C/C++) function that we can invoke at runtime + * 1. (always) primitive refers to a compiled (C/C++) function that we can invoke at runtime + * 2. (sometimes) primitive also refers to a function that is supported directly in llvm + * (e.g. floating-point addition). In that case @ref intrinsic_ + * identifies that direct support, provided it knows at codegen time which primitive + * is being invoked * * In any case, a primitive serves as both declaration and definition * (May be possible to relax this to declaration-only using null value_ as sentinel..?) @@ -36,13 +39,15 @@ namespace xo { public: static ref::rp make(const std::string & name, FunctionPointer fnptr, - bool explicit_symbol_def) { + bool explicit_symbol_def, + llvmintrinsic intrinsic) { TypeDescr fn_type = Reflect::require(); - return new Primitive(fn_type, name, fnptr, explicit_symbol_def); + return new Primitive(fn_type, name, fnptr, explicit_symbol_def, intrinsic); } FunctionPointer value() const { return value_; } + llvmintrinsic intrinsic() const { return intrinsic_; } TypeDescr value_td() const { return value_td_; } TaggedPtr value_tp() const { @@ -79,12 +84,14 @@ namespace xo { Primitive(TypeDescr fn_type, const std::string & name, FunctionPointer fnptr, - bool explicit_symbol_def) + bool explicit_symbol_def, + llvmintrinsic intrinsic) : PrimitiveInterface(fn_type), name_{name}, value_td_{Reflect::require_function()}, value_{fnptr}, - explicit_symbol_def_{explicit_symbol_def} + explicit_symbol_def_{explicit_symbol_def}, + intrinsic_{intrinsic} { if (!value_td_->is_function()) throw std::runtime_error("Primitive: expected function pointer"); @@ -103,11 +110,16 @@ namespace xo { TypeDescr value_td_; /** address of executable function **/ FunctionPointer value_; - /** if true, use Jit.intern_symbol() to provide explicit binding. - * Currently mystified as to what's distinguishes functions like ::sin(), ::sqrt() - * (which work do not require this) from symbols like ::mul_i32(), which do) + /** for LLVM: if true, use Jit.intern_symbol() to provide explicit binding. + * + * Not obvious what distinguishes functions like ::sin(), ::sqrt() + * (which work without this) from symbols like ::mul_i32(), which do. **/ bool explicit_symbol_def_ = false; + /** invalid: generate call (IRBuilder::CreateCall) + * all others: generate direct use of LLVM intrinsic + **/ + llvmintrinsic intrinsic_; }; /*Primitive*/ /** adopt function @p x as a callable primitive function named @p name **/ @@ -115,9 +127,10 @@ namespace xo { ref::rp> make_primitive(const std::string & name, FunctionPointer x, - bool explicit_symbol_def) + bool explicit_symbol_def, + llvmintrinsic intrinsic) { - return Primitive::make(name, x, explicit_symbol_def); + return Primitive::make(name, x, explicit_symbol_def, intrinsic); } } /*namespace ast*/ } /*namespace xo*/ diff --git a/include/xo/expression/PrimitiveInterface.hpp b/include/xo/expression/PrimitiveInterface.hpp index 0b42b252..629772e0 100644 --- a/include/xo/expression/PrimitiveInterface.hpp +++ b/include/xo/expression/PrimitiveInterface.hpp @@ -6,6 +6,7 @@ #pragma once #include "FunctionInterface.hpp" +#include "llvmintrinsic.hpp" //#include @@ -34,10 +35,12 @@ namespace xo { **/ virtual bool explicit_symbol_def() const = 0; - /** function address for this primitive - **/ + /** function address for this primitive **/ virtual void_function_type function_address() const = 0; + /** get llvm intrinsic hint for this primitive **/ + virtual llvmintrinsic intrinsic() const = 0; + // virtual const std::string & name() const; // virtual int n_arg() const; // virtual TypeDescr fn_retval() const; From f4efdd970060c9b6d96495b8a47f8dbee6087df8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 14:06:26 -0400 Subject: [PATCH 1155/2524] xo-pyexpression: supply llvm intrinsic to each primitive --- src/pyexpression/pyexpression.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index 10307d68..0768c502 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -118,17 +118,20 @@ namespace xo { m.def("make_sqrt_pm", []() { return make_primitive("sqrt", ::sqrt, - false /*!explicit_symbol_def*/); }, + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sqrt); }, py::doc("create primitive representing the ::sqrt() function")); m.def("make_sin_pm", []() { return make_primitive("sin", ::sin, - false /*!explicit_symbol_def*/); }, + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sin); }, py::doc("create primitive representing the ::sin() function")); m.def("make_cos_pm", []() { return make_primitive("cos", ::cos, - false /*!explicit_symbol_def*/); }, + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_cos); }, py::doc("create primitive representing the ::cos() function")); py::class_, @@ -139,7 +142,8 @@ namespace xo { m.def("make_pow_pm", []() { return make_primitive("pow", ::pow, - false /*!explicit_symbol_def*/); }, + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_pow); }, py::doc("create primitive representing the ::pow() function")); // ----- Apply ----- From 52ec4f09a586bd110d1628b0a3e30159f3a249d1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 14:07:43 -0400 Subject: [PATCH 1156/2524] xo-jit: supply llvmintrinsic to primitives --- example/ex1/ex1.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 6f5a7c35..0214a265 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -38,6 +38,7 @@ main() { using xo::jit::MachPipeline; using xo::ast::make_constant; using xo::ast::make_primitive; + using xo::ast::llvmintrinsic; using xo::ast::make_apply; using xo::ast::make_var; using xo::ast::make_lambda; @@ -82,7 +83,8 @@ main() { { auto expr = make_primitive("sqrt", &sqrt, - false /*!explicit_symbol_def*/); + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sqrt); log && log(xtag("expr", expr)); @@ -104,7 +106,8 @@ main() { /* (sqrt 2) */ auto fn = make_primitive("sqrt", &sqrt, - false /*!explicit_symbol_def*/); + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sqrt); auto arg = make_constant(2.0); auto call = make_apply(fn, {arg}); @@ -128,10 +131,14 @@ main() { { /* (lambda (x) (sin (cos x))) */ - auto sin = make_primitive("sin", ::sin, - false /*!explicit_symbol_def*/); - auto cos = make_primitive("cos", ::cos, - false /*!explicit_symbol_def*/); + auto sin = make_primitive("sin", + ::sin, + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sin); + auto cos = make_primitive("cos", + ::cos, + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_cos); auto x_var = make_var("x", Reflect::require()); auto call1 = make_apply(cos, {x_var}); /* (cos x) */ From 88cc8885b64545566a57bb4dbb35259682acff42 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 14:08:07 -0400 Subject: [PATCH 1157/2524] xo-jit: in MachPipeline use intrinsic for faster code path --- src/jit/MachPipeline.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 0baf2c3e..d0abc7c6 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -13,6 +13,7 @@ namespace xo { using xo::ast::Variable; using xo::ast::Apply; using xo::ast::IfExpr; + using xo::ast::llvmintrinsic; using xo::reflect::Reflect; using xo::reflect::TypeDescr; using std::cerr; @@ -295,6 +296,7 @@ namespace xo { || apply->fn()->extype() == exprtype::lambda) { llvm::Function * llvm_fn = nullptr; + llvmintrinsic intrinsic = llvmintrinsic::invalid; FunctionInterface * fn = nullptr; { // TODO: codgen_function() @@ -303,6 +305,8 @@ namespace xo { if (pm) { fn = pm.get(); llvm_fn = this->codegen_primitive(pm); + /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ + intrinsic = pm->intrinsic(); } auto lm = Lambda::from(apply->fn()); @@ -361,6 +365,25 @@ namespace xo { args.push_back(arg); } + switch(intrinsic) { + case llvmintrinsic::i_add: + return llvm_ir_builder_->CreateAdd(args[0], args[1]); + case llvmintrinsic::i_mul: + return llvm_ir_builder_->CreateMul(args[0], args[1]); + case llvmintrinsic::fp_add: + return llvm_ir_builder_->CreateFAdd(args[0], args[1]); + case llvmintrinsic::fp_mul: + return llvm_ir_builder_->CreateFMul(args[0], args[1]); + case llvmintrinsic::invalid: + case llvmintrinsic::fp_sqrt: + case llvmintrinsic::fp_pow: + case llvmintrinsic::fp_sin: + case llvmintrinsic::fp_cos: + case llvmintrinsic::fp_tan: + case llvmintrinsic::n_intrinsic: /* n_intrinsic: not reachable */ + break; + } + return llvm_ir_builder_->CreateCall(llvm_fn, args, "calltmp"); } else { cerr << "MachPipeline::codegen_apply: error: only allowing call to known primitives at present" << endl; From ea681a65eaa1b8716686a1dc6a8bb0975b02a977 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 14:08:45 -0400 Subject: [PATCH 1158/2524] xo-jit: doc: + HOWTO --- HOWTO | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 HOWTO diff --git a/HOWTO b/HOWTO new file mode 100644 index 00000000..6965105f --- /dev/null +++ b/HOWTO @@ -0,0 +1,45 @@ +* How to add an llvm intrinsic + + - add enum value llvmintrinsic::foo to llvmintrinsic in xo-expression + - also add foo to llvmintrinsic2str in xo-expression llvmintrinsic.hpp + - if we have a built-in Primitive for the same functionality, + want Primitive::intrinsic_ = llvmintrinsic::foo + - in MachPipeline::codegen_apply(), look for switch(intrinsic), + add case llvmintrinsic::foo + - substitute codegen for the intrinsic + in place of the catch-all IRBuilder::CreateCall + +** To test from python: + + 1. install xo-pyjit and deps somewhere (~/local2 in this example) + + 2. PYTHONPATH=~/local2:$PYTHONPATH python + + 3. python: + + from xo_pyreflect import * + from xo_pyexpression import * + from xo_pyjit import * + i32_t=TypeDescr.lookup_by_name('double') + x=make_var('x',i32_t) + f=make_mul_i32_pm() + c=make_apply(f,[x,x]) + lm=make_lambda('sq',[x],c) + + mp=MachPipeline.make() + code=mp.codegen(lm) + print(code.print()) + + 4. in our example, get output like: + + define i32 @sq(i32 %x) { + entry: + %0 = mul i32 %x, %x + ret i32 %0 + } + + 5. to compile+run via JIT: + + mp.machgen_current_module() + fn=mp.lookup_fn('int (*)(int)', 'sq') + fn(16) # -> 256 From bdd51539f447178ff07579c92e3e34a26b3ba2be Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 14:09:30 -0400 Subject: [PATCH 1159/2524] xo-pyjit: supply intrinsic to Primitive defns --- src/pyjit/pyjit.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 959f0931..184fb309 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -14,6 +14,7 @@ namespace xo { namespace jit { using xo::ast::Expression; using xo::ast::make_primitive; + using xo::ast::llvmintrinsic; using xo::pyutil::pycaller_base; using xo::pyutil::pycaller; using xo::ref::rp; @@ -123,7 +124,7 @@ namespace xo { []() { return make_primitive - ("mul_i32", ::mul_i32, true /*explicit_symbol_def*/); + ("mul_i32", ::mul_i32, true /*explicit_symbol_def*/, llvmintrinsic::i_mul); }, py::doc("create primitive for 32-bit signed integer multiplication")); @@ -131,7 +132,7 @@ namespace xo { []() { return make_primitive - ("mul_f64", ::mul_f64, true /*explicit_symbol_def*/); + ("mul_f64", ::mul_f64, true /*explicit_symbol_def*/, llvmintrinsic::fp_mul); }, py::doc("create primitive for 64-bit floating point multiplication")); From aae67dd79462591eede58c360ee4743f0dbd234e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 14:09:53 -0400 Subject: [PATCH 1160/2524] xo-pyjit: doc: fix atavism in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a749ee43..61a2d0c5 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ JITDylib "
" (ES: 0x0000000000446ee0, State = Open) Link order: [ ("
", MatchAllSymbols) ] Symbol table: "foo": [Callable] Never-Searched (Materializer 0x646fe0, xojit) ->>> fn=mp.lookup_dbl2dbl_fn('foo') +>>> fn=mp.lookup_fn('double (*)(double, double)', 'foo') >>> mp.dump_execution_session() JITDylib "
" (ES: 0x0000000000446ee0, State = Open) From e671686a3a0c65208ae01f31679d448a3a32b9e6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 17:00:06 -0400 Subject: [PATCH 1161/2524] xo-jit: refactor MachPipeline to use stack for lambda formals --- include/xo/jit/MachPipeline.hpp | 18 ++++++++++++- src/jit/MachPipeline.cpp | 46 +++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 967e3127..75244b93 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -54,6 +54,7 @@ namespace xo { class MachPipeline : public ref::Refcount { public: using Expression = xo::ast::Expression; + using TypeDescr = xo::reflect::TypeDescr; //using ConstantInterface = xo::ast::ConstantInterface; public: @@ -111,6 +112,16 @@ namespace xo { /** iniitialize native builder (i.e. for platform we're running on) **/ static void init_once(); + /** codegen helper for a user-defined function (codegen_lambda()): + * create stack slot on behalf of some formal parameter to a function, + * so we can avoid SSA restriction on function body + * + * @p var_type. variable type + **/ + llvm::AllocaInst * create_entry_block_alloca(llvm::Function * llvm_fn, + const std::string & var_name, + TypeDescr var_type); + /** (re)create pipeline to turn expressions into llvm IR code **/ void recreate_llvm_ir_pipeline(); @@ -155,8 +166,13 @@ namespace xo { * corresponding llvm IR. * * only supports one level atm (i.e. only top-level functions) + * + * All values live on the stack, so that we can evade single-assignment + * restrictions. + * + * rhs identifies logical stack location of a variable **/ - std::map nested_env_; + std::map nested_env_; /* <-> kaleidoscope NamedValues */ }; /*MachPipeline*/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index d0abc7c6..c1c65b37 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -391,6 +391,25 @@ namespace xo { } } /*codegen_apply*/ + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + llvm::AllocaInst * + MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, + const std::string & var_name, + TypeDescr var_type) + { + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), + llvm_fn->getEntryBlock().begin()); + + llvm::Type * llvm_var_type = td_to_llvm_type(llvm_cx_.borrow(), + var_type); + if (!llvm_var_type) + return nullptr; + + return tmp_ir_builder.CreateAlloca(llvm_var_type, + nullptr, + var_name); + } /*create_entry_block_alloca*/ + llvm::Function * MachPipeline::codegen_lambda(ref::brw lambda) { @@ -462,7 +481,25 @@ namespace xo { for (auto & arg : fn->args()) { log && log("nested environment", xtag("i", i), xtag("param", std::string(arg.getName()))); - nested_env_[std::string(arg.getName())] = &arg; + /* stack location for arg[i] */ + llvm::AllocaInst * alloca + = create_entry_block_alloca(fn, + std::string(arg.getName()), + lambda->fn_arg(i)); + + if (!alloca) + return nullptr; + + /* store on function entry + * see codegen_variable() for corresponding load + */ + this->llvm_ir_builder_->CreateStore(&arg, alloca); + + /* remember stack location for reference + assignment + * in lambda body. + * + */ + nested_env_[std::string(arg.getName())] = alloca; ++i; } } @@ -500,7 +537,12 @@ namespace xo { return nullptr; } - return ix->second; + llvm::AllocaInst * alloca = ix->second; + + /* code to load value from stack */ + return this->llvm_ir_builder_->CreateLoad(alloca->getAllocatedType(), + alloca, + var->name().c_str()); } /*codegen_variable*/ llvm::Value * From f1de52b96200abdf1c3a06a469e8ebcb25d43c64 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 17:00:59 -0400 Subject: [PATCH 1162/2524] xo-jit: cosmetic: code layout --- src/jit/MachPipeline.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index c1c65b37..1720b545 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -440,12 +440,14 @@ namespace xo { // PLACEHOLDER // just handle double arguments + return type for now - llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), lambda->fn_retval()); + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_retval()); std::vector arg_type_v(lambda->n_arg()); for (size_t i = 0, n = lambda->n_arg(); i < n; ++i) { - arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), lambda->fn_arg(i)); + arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_arg(i)); } auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, @@ -461,7 +463,9 @@ namespace xo { { int i = 0; for (auto & arg : fn->args()) { - log && log("llvm format param names", xtag("i", i), xtag("param", lambda->argv().at(i))); + log && log("llvm format param names", + xtag("i", i), + xtag("param", lambda->argv().at(i))); arg.setName(lambda->argv().at(i)->name()); ++i; @@ -479,7 +483,9 @@ namespace xo { { int i = 0; for (auto & arg : fn->args()) { - log && log("nested environment", xtag("i", i), xtag("param", std::string(arg.getName()))); + log && log("nested environment", + xtag("i", i), + xtag("param", std::string(arg.getName()))); /* stack location for arg[i] */ llvm::AllocaInst * alloca From 6abede9c335c46fe1d9bce5b6d2a7fbee97b5a63 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 17:01:11 -0400 Subject: [PATCH 1163/2524] xo-jit: print IR before- and after- optimization --- src/jit/MachPipeline.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 1720b545..293f8625 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -519,9 +519,25 @@ namespace xo { /* validate! always validate! */ llvm::verifyFunction(*fn); + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + fn->print(ss); + + log(xtag("IR-before-opt", buf)); + } + /* optimize! improves IR */ ir_pipeline_->run_pipeline(*fn); // llvm_fpmgr_->run(*fn, *llvm_famgr_); + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + fn->print(ss); + + log(xtag("IR-after-opt", buf)); + } + return fn; } From 2235bba87286d55ebcef4030f4747c43236f7ef4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 17:01:30 -0400 Subject: [PATCH 1164/2524] xo-jit: add mem-to-register pass to IrPipeline --- include/xo/jit/IrPipeline.hpp | 1 + src/jit/IrPipeline.cpp | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/include/xo/jit/IrPipeline.hpp b/include/xo/jit/IrPipeline.hpp index 7efd5b4f..60473bc5 100644 --- a/include/xo/jit/IrPipeline.hpp +++ b/include/xo/jit/IrPipeline.hpp @@ -28,6 +28,7 @@ #include "llvm/Transforms/InstCombine/InstCombine.h" #include "llvm/Transforms/Scalar.h" #include "llvm/Transforms/Scalar/GVN.h" +#include "llvm/Transforms/Utils/Mem2Reg.h" #include "llvm/Transforms/Scalar/Reassociate.h" #include "llvm/Transforms/Scalar/SimplifyCFG.h" diff --git a/src/jit/IrPipeline.cpp b/src/jit/IrPipeline.cpp index d08d9639..285edbe3 100644 --- a/src/jit/IrPipeline.cpp +++ b/src/jit/IrPipeline.cpp @@ -24,6 +24,13 @@ namespace xo { /** transform passes **/ this->llvm_fpmgr_->addPass(llvm::InstCombinePass()); + + /* NOTE: llvm 19 adds mem2reg transform here. + * speculating that PromotePass() does same/goodenough thing in llvm 18. + * This pays off, works first try! + */ + this->llvm_fpmgr_->addPass(llvm::PromotePass()); + this->llvm_fpmgr_->addPass(llvm::ReassociatePass()); this->llvm_fpmgr_->addPass(llvm::GVNPass()); this->llvm_fpmgr_->addPass(llvm::SimplifyCFGPass()); From e246f12d705935fdba1abf34d7fad96f77166ba6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 24 Jun 2024 14:02:47 -0400 Subject: [PATCH 1165/2524] xo-jit: refactor to support function pointer arguments. --- example/ex1/ex1.cpp | 8 +- include/xo/jit/MachPipeline.hpp | 40 +- include/xo/jit/activation_record.hpp | 38 ++ src/jit/CMakeLists.txt | 1 + src/jit/MachPipeline.cpp | 614 ++++++++++++++++++--------- src/jit/activation_record.cpp | 45 ++ 6 files changed, 522 insertions(+), 224 deletions(-) create mode 100644 include/xo/jit/activation_record.hpp create mode 100644 src/jit/activation_record.cpp diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 0214a265..22e1b0b7 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -67,7 +67,7 @@ main() { log && log(xtag("expr", expr)); - auto llvm_ircode = jit->codegen(expr); + auto llvm_ircode = jit->codegen_toplevel(expr); if (llvm_ircode) { /* note: llvm:errs() is 'raw stderr stream' */ @@ -88,7 +88,7 @@ main() { log && log(xtag("expr", expr)); - auto llvm_ircode = jit->codegen(expr); + auto llvm_ircode = jit->codegen_toplevel(expr); if (llvm_ircode) { /* note: llvm:errs() is 'raw stderr stream' */ @@ -114,7 +114,7 @@ main() { log && log(xtag("expr", call)); - auto llvm_ircode = jit->codegen(call); + auto llvm_ircode = jit->codegen_toplevel(call); if (llvm_ircode) { /* note: llvm:errs() is 'raw stderr stream' */ @@ -151,7 +151,7 @@ main() { log && log(xtag("expr", lambda)); - auto llvm_ircode = jit->codegen(lambda); + auto llvm_ircode = jit->codegen_toplevel(lambda); if (llvm_ircode) { /* note: llvm:errs() is 'raw stderr stream' */ diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 75244b93..2497fb5f 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -11,6 +11,7 @@ #include "IrPipeline.hpp" #include "LlvmContext.hpp" #include "Jit.hpp" +#include "activation_record.hpp" #include "xo/expression/Expression.hpp" #include "xo/expression/ConstantInterface.hpp" @@ -54,6 +55,7 @@ namespace xo { class MachPipeline : public ref::Refcount { public: using Expression = xo::ast::Expression; + using Lambda = xo::ast::Lambda; using TypeDescr = xo::reflect::TypeDescr; //using ConstantInterface = xo::ast::ConstantInterface; @@ -64,6 +66,10 @@ namespace xo { // ----- module access ----- + llvm::Module * current_module() { return llvm_module_.get(); } + ref::brw llvm_cx() { return llvm_cx_; } + llvm::IRBuilder<> * llvm_current_ir_builder() { return llvm_toplevel_ir_builder_.get(); } + /** target triple = string describing target host for codegen **/ const std::string & target_triple() const; /** append function names defined in attached module to *p_v @@ -75,16 +81,22 @@ namespace xo { /** write state of execution session (all the associated dynamic libraries) **/ void dump_execution_session(); - // ----- jit code generation ----- + // ----- code generation ----- llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); - llvm::Value * codegen_apply(ref::brw expr); - llvm::Function * codegen_lambda(ref::brw expr); - llvm::Value * codegen_variable(ref::brw var); - llvm::Value * codegen_ifexpr(ref::brw ifexpr); + llvm::Value * codegen_apply(ref::brw expr, llvm::IRBuilder<> & ir_builder); + /* NOTE: codegen_lambda() needs to be reentrant too. + * for example can have a lambda in apply position. + */ + llvm::Function * codegen_lambda_decl(ref::brw expr); + llvm::Function * codegen_lambda_defn(ref::brw expr, llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_variable(ref::brw var, llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_ifexpr(ref::brw ifexpr, llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen(ref::brw expr); + llvm::Value * codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder); + + llvm::Value * codegen_toplevel(ref::brw expr); // ----- jit online execution ----- @@ -112,6 +124,10 @@ namespace xo { /** iniitialize native builder (i.e. for platform we're running on) **/ static void init_once(); + /** helper function. find all lambda expressions in AST @p expr **/ + std::vector> find_lambdas(ref::brw expr) const; + + public: /** codegen helper for a user-defined function (codegen_lambda()): * create stack slot on behalf of some formal parameter to a function, * so we can avoid SSA restriction on function body @@ -122,6 +138,7 @@ namespace xo { const std::string & var_name, TypeDescr var_type); + private: /** (re)create pipeline to turn expressions into llvm IR code **/ void recreate_llvm_ir_pipeline(); @@ -135,6 +152,7 @@ namespace xo { // ----- this part adapted from kaleidoscope.cpp ----- + public: /** everything below represents a pipeline * that takes expressions, and turns them into llvm IR. * @@ -144,6 +162,7 @@ namespace xo { **/ xo::ref::rp ir_pipeline_; + private: /** owns + manages core "global" llvm data, * including type- and constant- unique-ing tables. * @@ -151,8 +170,10 @@ namespace xo { * each with its own LLVMContext **/ ref::rp llvm_cx_; + /** builder for intermediate-representation objects **/ - std::unique_ptr> llvm_ir_builder_; + std::unique_ptr> llvm_toplevel_ir_builder_; + /** a module (1:1 with library ?) being prepared by llvm. * IR-level -- does not contain machine code * @@ -162,6 +183,8 @@ namespace xo { /** map global names to functions/variables **/ std::map> global_env_; + + public: /** map variable names (formal parameters) to * corresponding llvm IR. * @@ -172,8 +195,7 @@ namespace xo { * * rhs identifies logical stack location of a variable **/ - std::map nested_env_; /* <-> kaleidoscope NamedValues */ - + std::stack env_stack_; /* <-> kaleidoscope NamedValues */ }; /*MachPipeline*/ inline std::ostream & diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp new file mode 100644 index 00000000..eb98975e --- /dev/null +++ b/include/xo/jit/activation_record.hpp @@ -0,0 +1,38 @@ +/** @file activation_record.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "LlvmContext.hpp" +#include +#include +#include +//#include + +namespace xo { + namespace jit { + /** scope for a stack frame associated with a user-defined function + * + * each function needs its own IR builder, to keep track of things like insert point + **/ + class activation_record { + public: + activation_record() = default; + + llvm::AllocaInst * lookup_var(const std::string & var_name) const; + + llvm::AllocaInst * alloc_var(const std::string & var_name, + llvm::AllocaInst * alloca); + + private: + /** maps named slots in a stack frame to logical addresses **/ + std::map frame_; /* <-> kaleidoscope NamedValues */ + }; /*activation_record*/ + + } /*namespace jit*/ +} /*namespace xo*/ + + +/** end activation_record.hpp **/ diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index aa0a3deb..fe0b9e3d 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -6,6 +6,7 @@ set(SELF_SRCS IrPipeline.cpp MachPipeline.cpp intrinsics.cpp + activation_record.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 293f8625..f19d92b1 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -76,7 +76,7 @@ namespace xo { { //llvm_cx_ = std::make_unique(); llvm_cx_ = LlvmContext::make(); - llvm_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); + llvm_toplevel_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); llvm_module_->setDataLayout(this->jit_->data_layout()); @@ -84,7 +84,7 @@ namespace xo { if (!llvm_cx_.get()) { throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); } - if (!llvm_ir_builder_.get()) { + if (!llvm_toplevel_ir_builder_.get()) { throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm IR builder"); } if (!llvm_module_.get()) { @@ -142,11 +142,71 @@ namespace xo { } /*codegen_constant*/ namespace { + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td); + + /** obtain llvm representation for a function type with the same signature as + * that represented by @p fn_td + **/ + llvm::FunctionType * + function_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + int n_fn_arg = fn_td->n_fn_arg(); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(n_fn_arg); + + /** check function args are all known **/ + for (int i = 0; i < n_fn_arg; ++i) { + TypeDescr arg_td = fn_td->fn_arg(i); + + llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx, arg_td); + + if (!llvm_argtype) + return nullptr; + + llvm_argtype_v.push_back(llvm_argtype); + } + + TypeDescr retval_td = fn_td->fn_retval(); + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx, retval_td); + + if (!llvm_retval) + return nullptr; + + auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, + llvm_argtype_v, + false /*!varargs*/); + return llvm_fn_type; + } + + llvm::PointerType * + function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); + + /** like C: llvm IR doesn't support function-valued variables; + * it does however support pointer-to-function-valued variables + **/ + auto * llvm_ptr_type + = llvm::PointerType::get(llvm_fn_type, + 0 /*numbered address space*/); + + return llvm_ptr_type; + } + llvm::Type * td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); - if (Reflect::is_native(td)) { + if (td->is_function()) { + /* in this context, we're looking for a representation for a value, + * i.e. something that can be stored in a variable + */ + return function_td_to_llvm_fnptr_type(llvm_cx, td); + } else if (Reflect::is_native(td)) { return llvm::Type::getInt1Ty(llvm_cx_ref); } else if (Reflect::is_native(td)) { return llvm::Type::getInt8Ty(llvm_cx_ref); @@ -172,7 +232,7 @@ namespace xo { llvm::Function * MachPipeline::codegen_primitive(ref::brw expr) { - constexpr bool c_debug_flag = true; + //constexpr bool c_debug_flag = true; using xo::scope; /** note: documentation (such as it is) for llvm::Function here: @@ -193,49 +253,14 @@ namespace xo { /** establish prototype for this function **/ - // PLACEHOLDER - // just make prototype for function :: double^n -> double - TypeDescr fn_td = expr->valuetype(); - int n_fn_arg = fn_td->n_fn_arg(); - scope log(XO_DEBUG(c_debug_flag), - xtag("fn_td", fn_td->short_name()), - xtag("n_fn_arg", n_fn_arg)); + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); - std::vector llvm_argtype_v; - llvm_argtype_v.reserve(n_fn_arg); - - /** check function args are all known **/ - for (int i = 0; i < n_fn_arg; ++i) { - TypeDescr arg_td = fn_td->fn_arg(i); - - log && log(xtag("i_arg", i), - xtag("arg_td", arg_td->short_name())); - - llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx_.borrow(), arg_td); - - if (!llvm_argtype) - return nullptr; - - llvm_argtype_v.push_back(llvm_argtype); - } - - //std::vector double_v(n_fn_arg, llvm::Type::getDoubleTy(*llvm_cx_)); - - TypeDescr retval_td = fn_td->fn_retval(); - - log && log(xtag("retval_td", retval_td->short_name())); - - llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), retval_td); - - if (!llvm_retval) + if (!llvm_fn_type) return nullptr; - auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, - llvm_argtype_v, - false /*!varargs*/); - fn = llvm::Function::Create(llvm_fn_type, llvm::Function::ExternalLinkage, expr->name(), @@ -274,121 +299,158 @@ namespace xo { #endif } +#ifdef OBSOLETE log && log("returning llvm function"); +#endif return fn; } /*codegen_primitive*/ llvm::Value * - MachPipeline::codegen_apply(ref::brw apply) + MachPipeline::codegen_apply(ref::brw apply, + llvm::IRBuilder<> & ir_builder) { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("apply", apply)); + + // see here: + // https://stackoverflow.com/questions/54905211/how-to-implement-function-pointer-by-using-llvm-c-api + using std::cerr; using std::endl; - /* editorial: - * - * to handle (computed functions) properly, - * we will need a runtime representation for a 'primitive function pointer' - * - * For now, finesse by only handling PrimitiveInterface in function-callee position + /* IR for value in function position. + * Although it will generate a function (or pointer-to-function), + * it need not have inherited type llvm::Function. */ - if (apply->fn()->extype() == exprtype::primitive - || apply->fn()->extype() == exprtype::lambda) + llvm::Value * llvm_fnval = nullptr; + llvmintrinsic intrinsic = llvmintrinsic::invalid; + /* function type in apply node's function position */ + TypeDescr ast_fn_td = apply->fn()->valuetype(); { - llvm::Function * llvm_fn = nullptr; - llvmintrinsic intrinsic = llvmintrinsic::invalid; - FunctionInterface * fn = nullptr; - { - // TODO: codgen_function() - + /* special treatement for primitive in apply position: + * allows substituting LLVM intrinsic + */ + if (apply->fn()->extype() == exprtype::primitive) { auto pm = PrimitiveInterface::from(apply->fn()); + if (pm) { - fn = pm.get(); - llvm_fn = this->codegen_primitive(pm); + llvm_fnval = this->codegen_primitive(pm); /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ intrinsic = pm->intrinsic(); } + } else { + llvm_fnval = this->codegen(apply->fn(), ir_builder); - auto lm = Lambda::from(apply->fn()); - if (lm) { - fn = lm.get(); - llvm_fn = this->codegen_lambda(lm); - } + /* we don't need any special checking here. + * already know (from xo-level checking) that pointer has the right type. + * + * Specifically, xo::ast::Apply::make() requires the expression in function position + * have suitable function type. + * + * Now: we have an llvm::Value (fn_value) representing the pointer. + * However it's not an llvm::Function instance, and we can't get one. + * + * (Older LLVM versions allowed getting the element type from a pointer, + * for some reasons that's deprecated at least in 18.1.5) + */ } + } - if (!llvm_fn) { - return nullptr; - } - -#ifdef NOT_USING_DEBUG - cerr << "MachPipeline::codegen_apply: fn:" << endl; - fn->print(llvm::errs()); - cerr << endl; -#endif - - if (llvm_fn->arg_size() != apply->argv().size()) { - cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" - << xtag("f", fn->name()) - << xtag("n1", fn->n_arg()) - << xtag("n2", apply->argv().size()) - << endl; - - return nullptr; - } - - /** also check argument types **/ - for (size_t i = 0, n = fn->n_arg(); i < n; ++i) { - if (apply->argv()[i]->valuetype() != fn->fn_arg(i)) { - cerr << "MachPipeline::codegen_apply: error: callee f for arg i seeeing U instead of expected T" - << xtag("f", fn->name()) - << xtag("i", i) - << xtag("U", apply->argv()[i]->valuetype()->short_name()) - << xtag("T", fn->fn_arg(i)->short_name()) - << endl; - - return nullptr; - } - } - - std::vector args; - args.reserve(apply->argv().size()); - - for (const auto & arg_expr : apply->argv()) { - auto * arg = this->codegen(arg_expr); - -#ifdef NOT_USING_DEBUG - cerr << "MachPipeline::codegen_apply: arg:" << endl; - arg->print(llvm::errs()); - cerr << endl; -#endif - - args.push_back(arg); - } - - switch(intrinsic) { - case llvmintrinsic::i_add: - return llvm_ir_builder_->CreateAdd(args[0], args[1]); - case llvmintrinsic::i_mul: - return llvm_ir_builder_->CreateMul(args[0], args[1]); - case llvmintrinsic::fp_add: - return llvm_ir_builder_->CreateFAdd(args[0], args[1]); - case llvmintrinsic::fp_mul: - return llvm_ir_builder_->CreateFMul(args[0], args[1]); - case llvmintrinsic::invalid: - case llvmintrinsic::fp_sqrt: - case llvmintrinsic::fp_pow: - case llvmintrinsic::fp_sin: - case llvmintrinsic::fp_cos: - case llvmintrinsic::fp_tan: - case llvmintrinsic::n_intrinsic: /* n_intrinsic: not reachable */ - break; - } - - return llvm_ir_builder_->CreateCall(llvm_fn, args, "calltmp"); - } else { - cerr << "MachPipeline::codegen_apply: error: only allowing call to known primitives at present" << endl; + if (!llvm_fnval) { return nullptr; } + +#ifdef NOT_USING_DEBUG + cerr << "MachPipeline::codegen_apply: fn:" << endl; + fn->print(llvm::errs()); + cerr << endl; +#endif + + /* checks here will be redundant */ + +#ifdef REDUNDANT_TYPECHECK + if (apply->argv().size() != ast_fn_td->n_fn_arg()) { + cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" + //<< xtag("f", ast_fn->name()) + << xtag("n1", ast_fn_td->n_fn_arg()) + << xtag("n2", apply->argv().size()) + << endl; + + return nullptr; + } + + /** also check argument types **/ + for (size_t i = 0, n = ast_fn_td->n_fn_arg(); i < n; ++i) { + if (apply->argv()[i]->valuetype() != ast_fn_td->fn_arg(i)) { + cerr << "MachPipeline::codegen_apply: error: callee F for arg# I seeeing U instead of expected T" + << xtag("F", apply->fn()) + << xtag("I", i) + << xtag("U", apply->argv()[i]->valuetype()->short_name()) + << xtag("T", ast_fn_td->fn_arg(i)->short_name()) + << endl; + + return nullptr; + } + } +#endif + + std::vector args; + args.reserve(apply->argv().size()); + + int i = 0; + for (const auto & arg_expr : apply->argv()) { + auto * arg = this->codegen(arg_expr, ir_builder); + + if (log) { + /* TODO: print helper for llvm::Value* */ + std::string llvm_value_str; + llvm::raw_string_ostream ss(llvm_value_str); + arg->print(ss); + + log(xtag("i_arg", i), + xtag("arg", llvm_value_str)); + } + + args.push_back(arg); + ++i; + } + + switch(intrinsic) { + case llvmintrinsic::i_add: + return ir_builder.CreateAdd(args[0], args[1]); + case llvmintrinsic::i_mul: + return ir_builder.CreateMul(args[0], args[1]); + case llvmintrinsic::fp_add: + return ir_builder.CreateFAdd(args[0], args[1]); + case llvmintrinsic::fp_mul: + return ir_builder.CreateFMul(args[0], args[1]); + case llvmintrinsic::invalid: + case llvmintrinsic::fp_sqrt: + case llvmintrinsic::fp_pow: + case llvmintrinsic::fp_sin: + case llvmintrinsic::fp_cos: + case llvmintrinsic::fp_tan: + case llvmintrinsic::n_intrinsic: /* n_intrinsic: not reachable */ + break; + } + + /* At least as of 18.1.5, LLVM needs us to supply function type + * when making a function call. In particular it doesn't remember + * the function type with each function pointer + */ + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); + + return ir_builder.CreateCall(llvm_fn_type, + llvm_fnval, + args, + "calltmp"); + } /*codegen_apply*/ /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ @@ -397,21 +459,61 @@ namespace xo { const std::string & var_name, TypeDescr var_type) { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn), + xtag("var_name", var_name), + xtag("var_type", var_type->short_name())); + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), llvm_fn->getEntryBlock().begin()); llvm::Type * llvm_var_type = td_to_llvm_type(llvm_cx_.borrow(), var_type); + + log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); + if (log) { + std::string llvm_var_type_str; + llvm::raw_string_ostream ss(llvm_var_type_str); + llvm_var_type->print(ss); + + log(xtag("llvm_var_type", llvm_var_type_str)); + } + if (!llvm_var_type) return nullptr; - return tmp_ir_builder.CreateAlloca(llvm_var_type, - nullptr, - var_name); + llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(llvm_var_type, + nullptr, + var_name); + log && log(xtag("alloca", (void*)retval), + xtag("align", retval->getAlign().value()), + xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); + + return retval; } /*create_entry_block_alloca*/ + + std::vector> + MachPipeline::find_lambdas(ref::brw expr) const + { + std::vector> retval_v; + + expr->visit_preorder( + [&retval_v](ref::brw x) + { + if (x->extype() == exprtype::lambda) { + retval_v.push_back(Lambda::from(x)); + } + }); + + return retval_v; + } /*find_lambdas*/ + llvm::Function * - MachPipeline::codegen_lambda(ref::brw lambda) + MachPipeline::codegen_lambda_decl(ref::brw lambda) { constexpr bool c_debug_flag = true; using xo::scope; @@ -419,27 +521,18 @@ namespace xo { scope log(XO_DEBUG(c_debug_flag), xtag("lambda-name", lambda->name())); - /* reminder! this is the *expression*, not the *closure* */ - global_env_[lambda->name()] = lambda.get(); /* do we already know a function with this name? */ auto * fn = llvm_module_->getFunction(lambda->name()); if (fn) { - /** function with this name already defined?? **/ - cerr << "MachPipeline::codegen_lambda: function f already defined" - << xtag("f", lambda->name()) - << endl; - - return nullptr; + return fn; } /* establish prototype for this function */ - // PLACEHOLDER - // just handle double arguments + return type for now - +#ifdef OBSOLETE llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), lambda->fn_retval()); @@ -449,10 +542,11 @@ namespace xo { arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), lambda->fn_arg(i)); } +#endif - auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, - arg_type_v, - false /*!varargs*/); + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), + lambda->valuetype()); /* create (initially empty) function */ fn = llvm::Function::Create(llvm_fn_type, @@ -463,7 +557,7 @@ namespace xo { { int i = 0; for (auto & arg : fn->args()) { - log && log("llvm format param names", + log && log("llvm formal param names", xtag("i", i), xtag("param", lambda->argv().at(i))); @@ -472,114 +566,158 @@ namespace xo { } } + return fn; + } /*codegen_lambda_decl*/ + + llvm::Function * + MachPipeline::codegen_lambda_defn(ref::brw lambda, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * llvm_fn = llvm_module_->getFunction(lambda->name()); + + if (!llvm_fn) { + /** function with this name not declared? **/ + cerr << "MachPipeline::codegen_lambda: function f not declared" + << xtag("f", lambda->name()) + << endl; + + return nullptr; + } + + /* generate function body */ - auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", fn); + auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); - llvm_ir_builder_->SetInsertPoint(block); + ir_builder.SetInsertPoint(block); + + /** Actual parameters will need their own activation record. + * Track its shape here. + **/ + this->env_stack_.push(activation_record()); - /* formal parameters need to appear in named_value_map_ */ - nested_env_.clear(); { + log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); + int i = 0; - for (auto & arg : fn->args()) { + for (auto & arg : llvm_fn->args()) { log && log("nested environment", xtag("i", i), xtag("param", std::string(arg.getName()))); + std::string arg_name = std::string(arg.getName()); + /* stack location for arg[i] */ llvm::AllocaInst * alloca - = create_entry_block_alloca(fn, - std::string(arg.getName()), + = create_entry_block_alloca(llvm_fn, + arg_name, lambda->fn_arg(i)); - if (!alloca) + if (!alloca) { + this->env_stack_.pop(); return nullptr; + } /* store on function entry * see codegen_variable() for corresponding load */ - this->llvm_ir_builder_->CreateStore(&arg, alloca); + ir_builder.CreateStore(&arg, alloca); /* remember stack location for reference + assignment * in lambda body. * */ - nested_env_[std::string(arg.getName())] = alloca; + env_stack_.top().alloc_var(arg_name, alloca); ++i; } } - llvm::Value * retval = this->codegen(lambda->body()); + llvm::Value * retval = this->codegen(lambda->body(), ir_builder); if (retval) { /* completes the function.. */ - llvm_ir_builder_->CreateRet(retval); + ir_builder.CreateRet(retval); /* validate! always validate! */ - llvm::verifyFunction(*fn); + llvm::verifyFunction(*llvm_fn); if (log) { std::string buf; llvm::raw_string_ostream ss(buf); - fn->print(ss); + llvm_fn->print(ss); log(xtag("IR-before-opt", buf)); } /* optimize! improves IR */ - ir_pipeline_->run_pipeline(*fn); // llvm_fpmgr_->run(*fn, *llvm_famgr_); + ir_pipeline_->run_pipeline(*llvm_fn); // llvm_fpmgr_->run(*llvm_fn, *llvm_famgr_); if (log) { std::string buf; llvm::raw_string_ostream ss(buf); - fn->print(ss); + llvm_fn->print(ss); log(xtag("IR-after-opt", buf)); } + } else { + /* oops, something went wrong */ + llvm_fn->eraseFromParent(); - return fn; + llvm_fn = nullptr; } - /* oops, something went wrong */ - fn->eraseFromParent(); + this->env_stack_.pop(); - return nullptr; - } /*codegen_lambda*/ + log && log("after pop, env stack size Z", xtag("Z", env_stack_.size())); + + return llvm_fn; + } /*codegen_lambda_defn*/ llvm::Value * - MachPipeline::codegen_variable(ref::brw var) + MachPipeline::codegen_variable(ref::brw var, + llvm::IRBuilder<> & ir_builder) { - auto ix = nested_env_.find(var->name()); - - if (ix == nested_env_.end()) { - cerr << "MachPipeline::codegen_variable: no binding for variable x" + if (env_stack_.empty()) { + cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" << xtag("x", var->name()) << endl; + return nullptr; } - llvm::AllocaInst * alloca = ix->second; + llvm::AllocaInst * alloca = env_stack_.top().lookup_var(var->name()); + + if (!alloca) + return nullptr; /* code to load value from stack */ - return this->llvm_ir_builder_->CreateLoad(alloca->getAllocatedType(), - alloca, - var->name().c_str()); + return ir_builder.CreateLoad(alloca->getAllocatedType(), + alloca, + var->name().c_str()); } /*codegen_variable*/ llvm::Value * - MachPipeline::codegen_ifexpr(ref::brw expr) + MachPipeline::codegen_ifexpr(ref::brw expr, llvm::IRBuilder<> & ir_builder) { - llvm::Value * test_ir = this->codegen(expr->test()); + llvm::Value * test_ir = this->codegen(expr->test(), ir_builder); /** need test result in a variable **/ llvm::Value * test_with_cmp_ir - = llvm_ir_builder_->CreateFCmpONE(test_ir, - llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), - llvm::APFloat(0.0)), - "iftest"); + = ir_builder.CreateFCmpONE(test_ir, + llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(0.0)), + "iftest"); - llvm::Function * parent_fn = llvm_ir_builder_->GetInsertBlock()->getParent(); + llvm::Function * parent_fn = ir_builder.GetInsertBlock()->getParent(); /* when_true_bb, when_false_bb, merge_bb: * initially-empty basic-blocks for {when_true, when_false, merged} codegen @@ -601,45 +739,46 @@ namespace xo { /* IR to direct control flow to one of {when_true_bb, when_false_bb}, * depending on result of test_with_cmp_ir */ - llvm_ir_builder_->CreateCondBr(test_with_cmp_ir, - when_true_bb, - when_false_bb); + ir_builder.CreateCondBr(test_with_cmp_ir, + when_true_bb, + when_false_bb); /* populate when_true_bb */ - llvm_ir_builder_->SetInsertPoint(when_true_bb); + ir_builder.SetInsertPoint(when_true_bb); - llvm::Value * when_true_ir = this->codegen(expr->when_true()); + llvm::Value * when_true_ir = this->codegen(expr->when_true(), + ir_builder); if (!when_true_ir) return nullptr; /* at end of when-true sequence, jump to merge suffix */ - llvm_ir_builder_->CreateBr(merge_bb); + ir_builder.CreateBr(merge_bb); /* note: codegen for expr->when_true() may have altered builder's "current block" */ - when_true_bb = llvm_ir_builder_->GetInsertBlock(); + when_true_bb = ir_builder.GetInsertBlock(); /* populate when_false_bb */ parent_fn->insert(parent_fn->end(), when_false_bb); - llvm_ir_builder_->SetInsertPoint(when_false_bb); + ir_builder.SetInsertPoint(when_false_bb); - llvm::Value * when_false_ir = this->codegen(expr->when_false()); + llvm::Value * when_false_ir = this->codegen(expr->when_false(), ir_builder); if (!when_false_ir) return nullptr; /* at end of when-false sequence, jump to merge suffix */ - llvm_ir_builder_->CreateBr(merge_bb); + ir_builder.CreateBr(merge_bb); /* note: codegen for expr->when_false() may have altered builder's "current block" */ - when_false_bb = llvm_ir_builder_->GetInsertBlock(); + when_false_bb = ir_builder.GetInsertBlock(); /* merged suffix sequence */ parent_fn->insert(parent_fn->end(), merge_bb); - llvm_ir_builder_->SetInsertPoint(merge_bb); + ir_builder.SetInsertPoint(merge_bb); /** TODO: switch to getInt1Ty here **/ llvm::PHINode * phi_node - = llvm_ir_builder_->CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), - 2 /*#of branches being merged (?)*/, - "iftmp"); + = ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), + 2 /*#of branches being merged (?)*/, + "iftmp"); phi_node->addIncoming(when_true_ir, when_true_bb); phi_node->addIncoming(when_false_ir, when_false_bb); @@ -647,7 +786,7 @@ namespace xo { } /*codegen_ifexpr*/ llvm::Value * - MachPipeline::codegen(ref::brw expr) + MachPipeline::codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder) { switch(expr->extype()) { case exprtype::constant: @@ -655,13 +794,13 @@ namespace xo { case exprtype::primitive: return this->codegen_primitive(PrimitiveInterface::from(expr)); case exprtype::apply: - return this->codegen_apply(Apply::from(expr)); + return this->codegen_apply(Apply::from(expr), ir_builder); case exprtype::lambda: - return this->codegen_lambda(Lambda::from(expr)); + return this->codegen_lambda_decl(Lambda::from(expr)); case exprtype::variable: - return this->codegen_variable(Variable::from(expr)); + return this->codegen_variable(Variable::from(expr), ir_builder); case exprtype::ifexpr: - return this->codegen_ifexpr(IfExpr::from(expr)); + return this->codegen_ifexpr(IfExpr::from(expr), ir_builder); case exprtype::invalid: case exprtype::n_expr: return nullptr; @@ -675,6 +814,59 @@ namespace xo { return nullptr; } /*codegen*/ + llvm::Value * + MachPipeline::codegen_toplevel(ref::brw expr) + { + /* - Pass 1. + * get set of lambdas. + * Generate decls for all. + * + * TODO: for lexical scoping (not implemented yet) + * will need traversal that maintains stack + * of ancestor lambdas, or at least their + * activation records. May want to generalize + * activation_record so we can track the set of variables + * before generating AllocaInst's. + * + * - Pass 2. + * Generate code for lambdas. + * + * - Pass 3. + * If toplevel expressions isn't a lambda + * (? won't make sense at present when called from python) + * generate code for it too + */ + + /* Pass 1. */ + auto fn_v = this->find_lambdas(expr); + + for (auto lambda : fn_v) { + this->codegen_lambda_decl(lambda); + } + + /* Pass 2 */ + for (auto lambda : fn_v) { + this->codegen_lambda_defn(lambda, + *(this->llvm_toplevel_ir_builder_.get())); + } + + /* Pass 3 */ + if (expr->extype() == exprtype::lambda) { + /* code already generated in pass 2; + * look it up + */ + + return llvm_module_->getFunction(Lambda::from(expr)->name()); + } else { + /* toplevel expression isn't a lambda, + * so code for it hasn't been generated. + * Do that now + */ + return this->codegen(expr, + *(this->llvm_toplevel_ir_builder_.get())); + } + } /*codegen_toplevel*/ + void MachPipeline::dump_current_module() { diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp new file mode 100644 index 00000000..c7f40362 --- /dev/null +++ b/src/jit/activation_record.cpp @@ -0,0 +1,45 @@ +/* @file activation_record.cpp */ + +#include "activation_record.hpp" +#include "xo/indentlog/print/tag.hpp" +#include + +namespace xo { + namespace jit { + using std::cerr; + using std::endl; + + llvm::AllocaInst * + activation_record::lookup_var(const std::string & x) const { + + auto ix = frame_.find(x); + + if (ix == frame_.end()) { + cerr << "activation_record::lookup_var: no binding for variable x" + << xtag("x", x) + << endl; + return nullptr; + } + + return ix->second; + } /*lookup_var*/ + + llvm::AllocaInst * + activation_record::alloc_var(const std::string & x, + llvm::AllocaInst * alloca) + { + if (frame_.find(x) != frame_.end()) { + cerr << "activation_record::alloc_var: variable x already present in frame" + << xtag("x", x) + << endl; + return nullptr; + } + + frame_[x] = alloca; + return alloca; + } /*alloc_var*/ + } /*namespace jit*/ +} /*namespace xo*/ + + +/* end activation_record.cpp */ From 2f593d15d55cd00dc6c6ecee44374ecc38fa59d9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 24 Jun 2024 14:03:19 -0400 Subject: [PATCH 1166/2524] xo-jit: + 2 examples --- example/CMakeLists.txt | 2 + example/ex2_jit/CMakeLists.txt | 12 +++ example/ex2_jit/ex2_jit.cpp | 170 ++++++++++++++++++++++++++++++++ example/ex3_fptr/CMakeLists.txt | 12 +++ example/ex3_fptr/ex3_fptr.cpp | 45 +++++++++ 5 files changed, 241 insertions(+) create mode 100644 example/ex2_jit/CMakeLists.txt create mode 100644 example/ex2_jit/ex2_jit.cpp create mode 100644 example/ex3_fptr/CMakeLists.txt create mode 100644 example/ex3_fptr/ex3_fptr.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 0cb5ee51..168ffa7c 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,2 +1,4 @@ add_subdirectory(ex1) +add_subdirectory(ex2_jit) +add_subdirectory(ex3_fptr) add_subdirectory(ex_kaleidoscope4) diff --git a/example/ex2_jit/CMakeLists.txt b/example/ex2_jit/CMakeLists.txt new file mode 100644 index 00000000..1a6d1e88 --- /dev/null +++ b/example/ex2_jit/CMakeLists.txt @@ -0,0 +1,12 @@ +# xo-jit/example/ex2_jit/CMakeLists.txt + +set(SELF_EXE xo_jit_ex2) +set(SELF_SRCS ex2_jit.cpp) + +if (XO_ENABLE_EXAMPLES) + xo_add_executable(${SELF_EXE} ${SELF_SRCS}) + xo_self_dependency(${SELF_EXE} xo_jit) + #xo_dependency(${SELF_EXE} xo_expression) +endif() + +# end CMakeLists.txt diff --git a/example/ex2_jit/ex2_jit.cpp b/example/ex2_jit/ex2_jit.cpp new file mode 100644 index 00000000..3131f823 --- /dev/null +++ b/example/ex2_jit/ex2_jit.cpp @@ -0,0 +1,170 @@ +/** @file ex2_jit.cpp **/ + +#include "xo/jit/MachPipeline.hpp" +#include "xo/jit/activation_record.hpp" +#include "xo/expression/Constant.hpp" +#include "xo/expression/Primitive.hpp" +#include "xo/expression/Apply.hpp" +#include "xo/expression/Lambda.hpp" +#include "xo/expression/Variable.hpp" +#include + +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/PassManager.h" +#include "llvm/IR/Type.h" +#include "llvm/IR/Verifier.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/StandardInstrumentations.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Target/TargetMachine.h" +#include "llvm/Transforms/InstCombine/InstCombine.h" +#include "llvm/Transforms/Scalar.h" +#include "llvm/Transforms/Scalar/GVN.h" +#include "llvm/Transforms/Scalar/Reassociate.h" +#include "llvm/Transforms/Scalar/SimplifyCFG.h" + +//double foo(double x) { return x; } + +int +main() { + using xo::scope; + using xo::jit::MachPipeline; + using xo::jit::activation_record; + using xo::ast::make_constant; + using xo::ast::make_primitive; + using xo::ast::llvmintrinsic; + using xo::ast::make_apply; + using xo::ast::make_var; + using xo::ast::make_lambda; + using xo::reflect::Reflect; + using xo::xtag; + using std::cerr; + using std::endl; + + //using xo::ast::make_constant; + + static llvm::ExitOnError llvm_exit_on_err; + + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); + + //auto jit = llvm_exit_on_err(Jit::make_aux()); + auto jit = MachPipeline::make(); + + //static_assert(std::is_function_v); + + scope log(XO_DEBUG(true)); + + /* try spelling everything out */ + + { + auto sqrt = make_primitive("sqrt", + ::sqrt, + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sqrt); + + { + auto llvm_ircode = jit->codegen_toplevel(sqrt); + + if (llvm_ircode) { + /* note: llvm:errs() is 'raw stderr stream' */ + cerr << "ex1 llvm_ircode:" << endl; + llvm_ircode->print(llvm::errs()); + cerr << endl; + } else { + cerr << "ex1: code generation failed" + << xtag("expr", sqrt) + << endl; + } + } + +#define CHOICE 2 + +#if CHOICE == 0 +#define FUNCTION_SYMBOL "callit" + /* def callit(f :: double->double, x :: double) { f(x); } */ + + auto f_var = make_var("f", Reflect::require()); + auto x_var = make_var("x", Reflect::require()); + auto call1 = make_apply(f_var, {x_var}); /* (f x) */ + //auto call2 = make_apply(f_var, {call1}); /* (f (f x)) */ + + auto lambda = make_lambda("callit", + {f_var, x_var}, + call1); +#elif CHOICE == 1 +#define FUNCTION_SYMBOL "root4" + /* def root4(x : double) { sqrt(sqrt(x)) } */ + + auto x_var = make_var("x", Reflect::require()); + auto call1 = make_apply(sqrt, {x_var}); + auto call2 = make_apply(sqrt, {call1}); + + auto lambda = make_lambda("root4", + {x_var}, + call2); +#elif CHOICE == 2 +#define FUNCTION_SYMBOL "twice" + auto root = make_primitive("sqrt", + ::sqrt, + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sqrt); + + /* def twice(f :: int->int, x :: int) { f(f(x)) } */ + auto f_var = make_var("f", Reflect::require()); + auto x_var = make_var("x", Reflect::require()); + auto call1 = make_apply(f_var, {x_var}); /* (f x) */ + auto call2 = make_apply(f_var, {call1}); /* (f (f x)) */ + + /* (define (twice f ::int->int x ::int) (f (f x))) */ + auto lambda = make_lambda("twice", + {f_var, x_var}, + call2); +#endif + + log && log(xtag("lambda", lambda)); + + auto llvm_ircode = jit->codegen_toplevel(lambda); + + if (llvm_ircode) { + /* note: llvm:errs() is 'raw stderr stream' */ + cerr << "ex1 llvm_ircode:" << endl; + llvm_ircode->print(llvm::errs()); + cerr << endl; + } else { + cerr << "ex1: code generation failed" + << xtag("expr", lambda) + << endl; + } + + jit->machgen_current_module(); + + /* is this in module? */ + cerr << "ex2: jit execution session" << endl; + jit->dump_execution_session(); + + auto fn = jit->lookup_symbol(FUNCTION_SYMBOL); + + if (!fn) { + cerr << "ex2: lookup: symbol not found" + << xtag("symbol", FUNCTION_SYMBOL) + << endl; + } else { + cerr << "ex2: lookup: symbol found" + << xtag("fn", fn.get().getValue()) + << xtag("symbol", FUNCTION_SYMBOL) + << endl; + } + } +} + +/** end ex2_jit.cpp **/ diff --git a/example/ex3_fptr/CMakeLists.txt b/example/ex3_fptr/CMakeLists.txt new file mode 100644 index 00000000..b66e7f86 --- /dev/null +++ b/example/ex3_fptr/CMakeLists.txt @@ -0,0 +1,12 @@ +# xo-jit/example/ex3_fptr/CMakeLists.txt + +set(SELF_EXE xo_fptr_ex3) +set(SELF_SRCS ex3_fptr.cpp) + +if (XO_ENABLE_EXAMPLES) + xo_add_executable(${SELF_EXE} ${SELF_SRCS}) + xo_self_dependency(${SELF_EXE} xo_jit) + #xo_dependency(${SELF_EXE} xo_expression) +endif() + +# end CMakeLists.txt diff --git a/example/ex3_fptr/ex3_fptr.cpp b/example/ex3_fptr/ex3_fptr.cpp new file mode 100644 index 00000000..ef6de58c --- /dev/null +++ b/example/ex3_fptr/ex3_fptr.cpp @@ -0,0 +1,45 @@ +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/IRBuilder.h" + +#include "llvm/Support/raw_ostream.h" + +int main() { + llvm::LLVMContext context; + llvm::IRBuilder<> builder(context); + llvm::Module *module = new llvm::Module("top", context); + + // Create main function and basic block + llvm::FunctionType *functionType = llvm::FunctionType::get(builder.getInt32Ty(), false); + llvm::Function *mainFunction = llvm::Function::Create(functionType, llvm::Function::ExternalLinkage, "main", module); + llvm::BasicBlock *entry = llvm::BasicBlock::Create(context, "entrypoint", mainFunction); + builder.SetInsertPoint(entry); + + // Create a global string pointer + llvm::Value *helloWorld = builder.CreateGlobalStringPtr("hello world\n"); + + // Create function pointer for puts + std::vector putArgs; + putArgs.push_back(builder.getInt8Ty()->getPointerTo()); + llvm::ArrayRef argsRef(putArgs); + llvm::FunctionType *putsType = llvm::FunctionType::get(builder.getInt32Ty(), argsRef, false); + /* = FunctionType + Callee-pointer */ + llvm::FunctionCallee putFunction_callee = module->getOrInsertFunction("puts", putsType); + +#ifdef NOT_YET + llvm::Constant * putFunction = llvm::Constant + + // Allocate memory for the function pointer + llvm::Value *p = builder.CreateAlloca(putFunction->getType(), nullptr, "p"); + builder.CreateStore(putFunction, p, false); + + // Load the function pointer and call it + llvm::Value *temp = builder.CreateLoad(p); + builder.CreateCall(temp, helloWorld); + + // Return 0 to complete the main function + builder.CreateRet(llvm::ConstantInt::get(builder.getInt32Ty(), 0)); + + // Print the module (IR code) + module->print(llvm::errs(), nullptr); +#endif +} From 5c7af2151b3f410528bd537c04ec2fe11f7960dd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 24 Jun 2024 14:03:30 -0400 Subject: [PATCH 1167/2524] xo-jit: + utest --- CMakeLists.txt | 2 +- utest/CMakeLists.txt | 15 +++ utest/MachPipeline.test.cpp | 188 ++++++++++++++++++++++++++++++++++++ utest/jit_utest_main.cpp | 6 ++ 4 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 utest/CMakeLists.txt create mode 100644 utest/MachPipeline.test.cpp create mode 100644 utest/jit_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 094529c0..3e1f3fb4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- add_subdirectory(example) -#add_subdirectory(utest) +add_subdirectory(utest) # ---------------------------------------------------------------- # docs targets depend on all the other library/utest targets diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..dbb64642 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,15 @@ +# xo-jit/utest/CMakeLists.txt + +set(SELF_EXE utest.jit) +set(SELF_SRCS + jit_utest_main.cpp + MachPipeline.test.cpp +) + +if (ENABLE_TESTING) + xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS}) + xo_self_dependency(${SELF_EXE} xo_jit) + xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) +endif() + +# end CMakeLists.txt diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp new file mode 100644 index 00000000..072cde2a --- /dev/null +++ b/utest/MachPipeline.test.cpp @@ -0,0 +1,188 @@ +/* @file MachPipeline.test.cpp */ + +#include "xo/jit/MachPipeline.hpp" +#include "xo/expression/Primitive.hpp" +#include "xo/indentlog/scope.hpp" +#include + +namespace xo { + using xo::jit::MachPipeline; + using xo::ast::make_apply; + using xo::ast::make_var; + using xo::ast::make_primitive; + using xo::ast::llvmintrinsic; + using xo::ast::Expression; + using xo::ast::Lambda; + using xo::ast::exprtype; + using xo::reflect::Reflect; + using xo::ref::rp; + using xo::ref::brw; + using std::cerr; + using std::endl; + + namespace ut { + + /* abstract syntax tree for a function: + * def root4(x :: double) { sqrt(sqrt(x)); } + */ + rp + root4_ast() { + auto sqrt = make_primitive("sqrt", + ::sqrt, + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sqrt); + auto x_var = make_var("x", Reflect::require()); + auto call1 = make_apply(sqrt, {x_var}); + auto call2 = make_apply(sqrt, {call1}); + + auto fn_ast = make_lambda("root4", + {x_var}, + call2); + + return fn_ast; + } + + /* abstract syntax tree for a function: + * def twice(f :: double->double, x :: double) { f(f(x)); } + */ + rp + root_2x_ast() { + auto root = make_primitive("sqrt", + ::sqrt, + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sqrt); + + /* def twice(f :: double->double, x :: double) { f(f(x)) } */ + auto f_var = make_var("f", Reflect::require()); + auto x_var = make_var("x", Reflect::require()); + auto call1 = make_apply(f_var, {x_var}); /* (f x) */ + auto call2 = make_apply(f_var, {call1}); /* (f (f x)) */ + + /* def twice(f :: double->double, x :: double) { f(f(x)); } */ + auto twice = make_lambda("twice", + {f_var, x_var}, + call2); + + auto x2_var = make_var("x2", Reflect::require()); + auto call3 = make_apply(twice, {root, x2_var}); + + /* def root4(x2 :: double) { + * def twice(f :: double->double, x :: double) { f(f(x)); }}; + * twice(sqrt, x2) + * } + */ + auto fn_ast = make_lambda("root_2x", + {x2_var}, + call3); + + return fn_ast; + } + + struct TestCase { + rp (*make_ast_)(); + /* each pair is (input, output) for function double->double */ + std::vector> call_v_; + }; + + static std::vector + s_testcase_v = { + {&root4_ast, + {std::make_pair(1.0, 1.0), + std::make_pair(16.0, 2.0), + std::make_pair(81.0, 3.0)}}, + {&root_2x_ast, + {std::make_pair(1.0, 1.0), + std::make_pair(16.0, 2.0), + std::make_pair(81.0, 3.0)}} + }; + + /** testcase root_ast tests: + * - nested function call + * + * testcase root_2x_ast relies on: + * - lambda in function position + * - argument with function type + **/ + TEST_CASE("machpipeline.fptr", "[llvm][llvm_fnptr]") { + constexpr bool c_debug_flag = true; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipeline")); + //log && log("(A)", xtag("foo", foo)); + + auto jit = MachPipeline::make(); + + for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + TestCase const & testcase = s_testcase_v[i_tc]; + + INFO(tostr(xtag("i_tc", i_tc))); + + auto ast = (*testcase.make_ast_)(); + + REQUIRE(ast.get()); + + log && log(xtag("ast", ast)); + + REQUIRE(ast->extype() == exprtype::lambda); + + brw fn_ast = Lambda::from(ast); + + llvm::Value * llvm_ircode = jit->codegen_toplevel(fn_ast); + + /* TODO: printer for llvm::Value* */ + if (llvm_ircode) { + /* note: llvm:errs() is 'raw stderr stream' */ + cerr << "llvm_ircode:" << endl; + llvm_ircode->print(llvm::errs()); + cerr << endl; + } else { + cerr << "code generation failed" + << xtag("fn_ast", fn_ast) + << endl; + } + + REQUIRE(llvm_ircode); + + jit->machgen_current_module(); + + /** lookup compiled function pointer in jit **/ + auto llvm_addr = jit->lookup_symbol(fn_ast->name()); + + if (!llvm_addr) { + cerr << "ex2: lookup: symbol not found" + << xtag("symbol", fn_ast->name()) + << endl; + } else { + cerr << "ex2: lookup: symbol found" + << xtag("llvm_addr", llvm_addr.get().getValue()) + << xtag("symbol", fn_ast->name()) + << endl; + } + + auto fn_ptr = llvm_addr.get().toPtr(); + + REQUIRE(fn_ptr); + + for (std::size_t j_call = 0, n_call = testcase.call_v_.size(); j_call < n_call; ++j_call) { + double input = testcase.call_v_[j_call].first; + double expected = testcase.call_v_[j_call].second; + + INFO(tostr(xtag("j_call", j_call), xtag("input", input), xtag("expected", expected))); + + auto actual = (*fn_ptr)(input); + + REQUIRE(actual == expected); + } + } + + } /*TEST_CASE(machpipeline)*/ + } /*namespace ut*/ +} /*namespace xo*/ + + +/* end MachPipeline.test.cpp */ diff --git a/utest/jit_utest_main.cpp b/utest/jit_utest_main.cpp new file mode 100644 index 00000000..1bea47b1 --- /dev/null +++ b/utest/jit_utest_main.cpp @@ -0,0 +1,6 @@ +/* @file jit_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include + +/* end jit_utest_main.cpp */ From d06f176c98e569e2d7c836517bf9d86e78d4fe4b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 24 Jun 2024 15:08:20 -0400 Subject: [PATCH 1168/2524] xo-jit: add sub/div intrinsics --- src/jit/MachPipeline.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index f19d92b1..1352387a 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -419,15 +419,29 @@ namespace xo { ++i; } + /* if we have an intrinsic hint, + * then instead of invoking a function, + * we use some native machine instruction instead. + */ switch(intrinsic) { + case llvmintrinsic::i_neg: + return ir_builder.CreateNeg(args[0]); case llvmintrinsic::i_add: return ir_builder.CreateAdd(args[0], args[1]); + case llvmintrinsic::i_sub: + return ir_builder.CreateSub(args[0], args[1]); case llvmintrinsic::i_mul: return ir_builder.CreateMul(args[0], args[1]); + case llvmintrinsic::i_sdiv: + return ir_builder.CreateSDiv(args[0], args[1]); + case llvmintrinsic::i_udiv: + return ir_builder.CreateUDiv(args[0], args[1]); case llvmintrinsic::fp_add: return ir_builder.CreateFAdd(args[0], args[1]); case llvmintrinsic::fp_mul: return ir_builder.CreateFMul(args[0], args[1]); + case llvmintrinsic::fp_div: + return ir_builder.CreateFDiv(args[0], args[1]); case llvmintrinsic::invalid: case llvmintrinsic::fp_sqrt: case llvmintrinsic::fp_pow: From d94e16eecdef5f6112b7cf0f1e42233a8ba92342 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 24 Jun 2024 15:09:27 -0400 Subject: [PATCH 1169/2524] xo-expression: + several arithmetic intrinsics --- include/xo/expression/llvmintrinsic.hpp | 37 +++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/include/xo/expression/llvmintrinsic.hpp b/include/xo/expression/llvmintrinsic.hpp index 37f231fe..052baa9e 100644 --- a/include/xo/expression/llvmintrinsic.hpp +++ b/include/xo/expression/llvmintrinsic.hpp @@ -19,9 +19,18 @@ namespace xo { * can use the associated llvm instrinsic instead of generating a function call * @c Primitive::value * - * @note llvm will still need to use @c Primitive::value, for example - * when handling an @c xo::ast::Apply instance where the function position - * is a computed function. + * @note llvm will still sometimes need to use + * @c Primitive::value (and generate a function call sequence), + * for example when handling an @c xo::ast::Apply instance + * where the function position is a computed function. + * @endnote + * + * @note + * LLVM requires separate intrinsics for {ints, floats}. + * It does not need separate intrinsics for different sizes. + * For example IRBuilder::CreateAdd works for + * {8-bit, 16-bit, 32-bit, 64-bit, 128-bit} x {signed, unsigned} integers + * Integer division is an exception; need to choose between i_sdiv and i_udiv * @endnote * * @note @@ -30,21 +39,38 @@ namespace xo { * @endnote **/ enum class llvmintrinsic { + // see /nix/store/x5yz...llvm-18.1.5-dev/include/llvm/IR/IRBuilder.h + /** sentinel value **/ invalid = -1, + /** -> IRBuilder::CreateNeg (negate 1 integer) **/ + i_neg, + /** -> IRBuilder::CreateAdd (add 2 integers, overflow silently) **/ i_add, + /** -> IRBuilder::CreateSub (subtract 2 integers, overflow silently) **/ + i_sub, + /** -> IRBuilder::CreateMul (multiply 2 integers, overflow silently) **/ i_mul, + /** -> IRBuilder::CreateSdiv (divide 2 signed integers) **/ + i_sdiv, + + /** -> IRBuilder::CreateUdiv (divide 2 unsigned integers) **/ + i_udiv, + /** -> IRBuilder::CreateFadd (add 2 floating-point numbers) **/ fp_add, /** -> IRBuilder::CreateFmul (multiply 2 floating-point numbers) **/ fp_mul, + /** -> IRBuilder::CreateFdiv (divide 2 floating-point numbers) **/ + fp_div, + /** * want to do whatever llvm IR @c llvm.sqrt.f64 and friends do. * Not sure if that's an always-available function of something else @@ -72,10 +98,15 @@ namespace xo { { switch(x) { case llvmintrinsic::invalid: return "?llvminstrinsic"; + case llvmintrinsic::i_neg: return "i_neg"; case llvmintrinsic::i_add: return "i_add"; + case llvmintrinsic::i_sub: return "i_sub"; case llvmintrinsic::i_mul: return "i_mul"; + case llvmintrinsic::i_sdiv: return "i_sdiv"; + case llvmintrinsic::i_udiv: return "i_udiv"; case llvmintrinsic::fp_add: return "fp_add"; case llvmintrinsic::fp_mul: return "fp_mul"; + case llvmintrinsic::fp_div: return "fp_div"; case llvmintrinsic::fp_sqrt: return "fp_sqrt"; case llvmintrinsic::fp_pow: return "fp_pow"; case llvmintrinsic::fp_sin: return "fp_sin"; From 829bffd007e7e594115361d87c7c1215b116e3f5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 24 Jun 2024 15:10:06 -0400 Subject: [PATCH 1170/2524] xo-expression: + Expression::visit_preorder() --- include/xo/expression/Apply.hpp | 15 ++++++++++++++- include/xo/expression/Constant.hpp | 5 +++++ include/xo/expression/Expression.hpp | 9 +++++++++ include/xo/expression/IfExpr.hpp | 11 +++++++++++ include/xo/expression/Lambda.hpp | 13 +++++++++++++ include/xo/expression/PrimitiveInterface.hpp | 7 +++++++ include/xo/expression/Variable.hpp | 7 ++++++- 7 files changed, 65 insertions(+), 2 deletions(-) diff --git a/include/xo/expression/Apply.hpp b/include/xo/expression/Apply.hpp index 775c31f9..b7cb8c31 100644 --- a/include/xo/expression/Apply.hpp +++ b/include/xo/expression/Apply.hpp @@ -36,7 +36,20 @@ namespace xo { const ref::rp & fn() const { return fn_; } const std::vector> & argv() const { return argv_; } - virtual void display(std::ostream & os) const; + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += fn_->visit_preorder(visitor_fn); + + for (const auto & arg : argv_) + n += arg->visit_preorder(visitor_fn); + + return n; + } + + virtual void display(std::ostream & os) const override; private: Apply(TypeDescr apply_valuetype, diff --git a/include/xo/expression/Constant.hpp b/include/xo/expression/Constant.hpp index 17d8aa3b..50f4c2ba 100644 --- a/include/xo/expression/Constant.hpp +++ b/include/xo/expression/Constant.hpp @@ -51,6 +51,11 @@ namespace xo { // ----- Expression ----- + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + visitor_fn(this); + return 1; + } + virtual void display(std::ostream & os) const override { os << "short_name()) diff --git a/include/xo/expression/Expression.hpp b/include/xo/expression/Expression.hpp index 22bd8d4a..b38eecbd 100644 --- a/include/xo/expression/Expression.hpp +++ b/include/xo/expression/Expression.hpp @@ -8,6 +8,7 @@ #include "xo/reflect/TypeDescr.hpp" #include "xo/refcnt/Refcounted.hpp" #include "exprtype.hpp" +#include namespace xo { namespace ast { @@ -29,6 +30,7 @@ namespace xo { **/ class Expression : public ref::Refcount { public: + using VisitFn = std::function)>; using TypeDescr = xo::reflect::TypeDescr; public: @@ -38,6 +40,13 @@ namespace xo { exprtype extype() const { return extype_; } TypeDescr valuetype() const { return valuetype_; } + /** visit each Expression node in this AST, + * and invoke @p fn for each. + * Returns the number of nodes visited. + * Preorder: call @p fn for a node before visiting children + **/ + virtual std::size_t visit_preorder(VisitFn visitor_fn) = 0; + /** write human-readable representation to stream **/ virtual void display(std::ostream & os) const = 0; /** human-readable string representation **/ diff --git a/include/xo/expression/IfExpr.hpp b/include/xo/expression/IfExpr.hpp index ff9c3e57..6f397b3d 100644 --- a/include/xo/expression/IfExpr.hpp +++ b/include/xo/expression/IfExpr.hpp @@ -40,6 +40,17 @@ namespace xo { // ----- Expression ----- + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += this->when_true_->visit_preorder(visitor_fn); + n += this->when_false_->visit_preorder(visitor_fn); + + return n; + } + virtual void display(std::ostream & os) const override; private: diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index ce63860d..20c4425b 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -48,6 +48,19 @@ namespace xo { // ----- Expression ----- + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + for (const auto & arg : argv_) + n += arg->visit_preorder(visitor_fn); + + n += body_->visit_preorder(visitor_fn); + + return n; + } + virtual void display(std::ostream & os) const override; private: diff --git a/include/xo/expression/PrimitiveInterface.hpp b/include/xo/expression/PrimitiveInterface.hpp index 629772e0..907aef2d 100644 --- a/include/xo/expression/PrimitiveInterface.hpp +++ b/include/xo/expression/PrimitiveInterface.hpp @@ -46,6 +46,13 @@ namespace xo { // virtual TypeDescr fn_retval() const; // virtual TypeDescr fn_arg(uint32_t i) const; + // ----- Expression ----- + + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + visitor_fn(this); + return 1; + } + private: }; /*PrimitiveInterface*/ } /*namespace ast*/ diff --git a/include/xo/expression/Variable.hpp b/include/xo/expression/Variable.hpp index 908efbf9..13e46a98 100644 --- a/include/xo/expression/Variable.hpp +++ b/include/xo/expression/Variable.hpp @@ -31,7 +31,12 @@ namespace xo { const std::string & name() const { return name_; } - virtual void display(std::ostream & os) const; + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + visitor_fn(this); + return 1; + } + + virtual void display(std::ostream & os) const override; private: Variable(const std::string & name, From be6d7c2aab322800650e5a27a360583453f1be71 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 24 Jun 2024 22:18:59 -0400 Subject: [PATCH 1171/2524] xo-jit: TypeDescr->llvm::Type conv for structs --- include/xo/jit/MachPipeline.hpp | 3 -- src/jit/MachPipeline.cpp | 76 +++++++++++++++++++++++++++++++++ utest/CMakeLists.txt | 2 + utest/MachPipeline.test.cpp | 25 ++++++++++- 4 files changed, 102 insertions(+), 4 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 2497fb5f..308397fc 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -152,7 +152,6 @@ namespace xo { // ----- this part adapted from kaleidoscope.cpp ----- - public: /** everything below represents a pipeline * that takes expressions, and turns them into llvm IR. * @@ -162,7 +161,6 @@ namespace xo { **/ xo::ref::rp ir_pipeline_; - private: /** owns + manages core "global" llvm data, * including type- and constant- unique-ing tables. * @@ -184,7 +182,6 @@ namespace xo { /** map global names to functions/variables **/ std::map> global_env_; - public: /** map variable names (formal parameters) to * corresponding llvm IR. * diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 1352387a..9a1a50bb 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -15,6 +15,7 @@ namespace xo { using xo::ast::IfExpr; using xo::ast::llvmintrinsic; using xo::reflect::Reflect; + using xo::reflect::StructMember; using xo::reflect::TypeDescr; using std::cerr; using std::endl; @@ -142,6 +143,12 @@ namespace xo { } /*codegen_constant*/ namespace { + /** REMINDER: + * 1. creation of llvm types is idempotent + * (duplicate calls will receive the same llvm::Type* pointer) + * 2. llvm::Types are never deleted. + **/ + llvm::Type * td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td); @@ -197,6 +204,73 @@ namespace xo { return llvm_ptr_type; } + /** + * Generate llvm::Type correspoinding to a TypeDescr for a struct. + **/ + llvm::StructType * + struct_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr struct_td) + { + // see + // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] + + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + /* note: object pointer ignored for struct types, + * since number of members is known at compile time + */ + int n_member = struct_td->n_child(nullptr /*&object*/); + + /* one type for each struct member */ + std::vector llvm_membertype_v; + llvm_membertype_v.reserve(n_member); + + for (int i = 0; i < n_member; ++i) { + StructMember const & sm = struct_td->struct_member(i); + + llvm_membertype_v.push_back(td_to_llvm_type(llvm_cx, + sm.get_member_td())); + } + + std::string struct_name = std::string(struct_td->short_name()); + + /* structs with names: within an llvmcontext, must be unique + * + * If we don't set isPacked, then padding will be chosen based on DataLayout, + * which might C++ compiler's padding, but no guarantees. + * + * We can however compare the offsets recorded in xo::reflect with + * offsets chosen by llvm, *once we've created the llvm type* + * + * Also, we can't guarantee that a c++ type was completely reflected -- + * it's possible one or more members were omitted, in which case + * it's unlikely at best that llvm chooses the same layout. + * + * Instead: tell llvm to make packed struct, + * and introduce dummy members for padding. + * + * A consequence is we have to maintain mapping between llvm's + * member numbering and xo::reflect's + */ + llvm::StructType * llvm_struct_type + = llvm::StructType::create(llvm_cx_ref, + llvm_membertype_v, + llvm::StringRef(struct_name), + true /*isPacked*/); + + /* TODO: inspect (how) offsets that llvm is using + * we need them to match what C++ chose + * + * (because we want jitted llvm code to interoperate with + * C++ library code that has structs) + */ + + // GetElementPtrInst is interesting, + // but I think that's for generating code + + return llvm_struct_type; + } /*struct_td_to_llvm_type*/ + llvm::Type * td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); @@ -206,6 +280,8 @@ namespace xo { * i.e. something that can be stored in a variable */ return function_td_to_llvm_fnptr_type(llvm_cx, td); + } else if (td->is_struct()) { + return struct_td_to_llvm_type(llvm_cx, td); } else if (Reflect::is_native(td)) { return llvm::Type::getInt1Ty(llvm_cx_ref); } else if (Reflect::is_native(td)) { diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index dbb64642..00614ddd 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -9,6 +9,8 @@ set(SELF_SRCS if (ENABLE_TESTING) xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS}) xo_self_dependency(${SELF_EXE} xo_jit) + xo_dependency(${SELF_EXE} xo_ratio) + xo_dependency(${SELF_EXE} xo_reflectutil) xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) endif() diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp index 072cde2a..c2c7f5bc 100644 --- a/utest/MachPipeline.test.cpp +++ b/utest/MachPipeline.test.cpp @@ -2,6 +2,8 @@ #include "xo/jit/MachPipeline.hpp" #include "xo/expression/Primitive.hpp" +#include "xo/ratio/ratio.hpp" +#include "xo/reflect/reflect_struct.hpp" #include "xo/indentlog/scope.hpp" #include @@ -15,6 +17,7 @@ namespace xo { using xo::ast::Lambda; using xo::ast::exprtype; using xo::reflect::Reflect; + using xo::reflect::reflect_struct; using xo::ref::rp; using xo::ref::brw; using std::cerr; @@ -179,8 +182,28 @@ namespace xo { REQUIRE(actual == expected); } } - } /*TEST_CASE(machpipeline)*/ + + TEST_CASE("machpipeline.struct", "[llvm][llvm_struct]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipeline.struct")); + //log && log("(A)", xtag("foo", foo)); + + auto jit = MachPipeline::make(); + + /* let's reflect xo::ratio::ratio */ + + auto struct_td = reflect_struct>(); + + REQUIRE(struct_td); + } /*TEST_CASE(machpipeline.struct)*/ } /*namespace ut*/ } /*namespace xo*/ From 37ff6c2b019849bed606a7e99acab1078ea85b28 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 24 Jun 2024 22:25:45 -0400 Subject: [PATCH 1172/2524] initial commit --- .gitignore | 8 + CMakeLists.txt | 46 +++++ LICENSE | 29 +++ cmake/xo-bootstrap-macros.cmake | 33 ++++ cmake/xo_reflectutilConfig.cmake.in | 17 ++ .../xo/reflectutil/reflect_struct_info.hpp | 175 ++++++++++++++++++ 6 files changed, 308 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_reflectutilConfig.cmake.in create mode 100644 include/xo/reflectutil/reflect_struct_info.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6f77977a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# emacs project control file +.projectile +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..a231f5dc --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,46 @@ +# xo-reflectutil/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_reflectutil VERSION 1.0) +enable_language(CXX) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- + +#add_subdirectory(example) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support for projects using this library + +set(SELF_LIB xo_reflectutil) +xo_add_headeronly_library(${SELF_LIB}) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- +# docs targets depend on all the other library/utest targets +# +#add_subdirectory(docs) + +# ---------------------------------------------------------------- +# dependencies + +xo_headeronly_dependency(${SELF_LIB} xo_flatstring) +#xo_headeronly_dependency(${SELF_LIB} randomgen) +# etc.. + +# end CMakeLists.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cae3cb5d --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2024 Roland Conybeare , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..2cf387e5 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,33 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) + message(FATAL "could not find xo-cmake-config executable") +endif() + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/cmake/xo_reflectutilConfig.cmake.in b/cmake/xo_reflectutilConfig.cmake.in new file mode 100644 index 00000000..b7a5a0a2 --- /dev/null +++ b/cmake/xo_reflectutilConfig.cmake.in @@ -0,0 +1,17 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +find_dependency(xo_flatstring) +#find_dependency(subsys) +#find_dependency(Eigen3) +#find_dependency(webutil) +#find_dependency(printjson) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/reflectutil/reflect_struct_info.hpp b/include/xo/reflectutil/reflect_struct_info.hpp new file mode 100644 index 00000000..98313088 --- /dev/null +++ b/include/xo/reflectutil/reflect_struct_info.hpp @@ -0,0 +1,175 @@ +/** @file reflect_struct_info.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include + +namespace xo { + namespace reflect { + /** Instructions for reflecting a struct: + * + * #include "xo/reflectutil/reflect_struct_info.hpp" // this file + * + * namespace xo { + * namespace reflect { + * // replacing X with the number of member variables in foo: + * REFLECT_BASE_STRUCT_INFO(some::ns::foo, X); + * + * // for each member {member_0, member_1, ...} of foo, + * // in the same order as they appear in class/struct decl: + * REFLECT_STRUCT_MEMBER_INFO(some::ns::foo, 0, member(0)); + * REFLECT_STRUCT_MEMBER_INFO(some::ns::foo, 1, member(1); + * .. + * // (last member will have index number X-1) + * } + * } + * + * These decls can appear anywhere after decl for some::ns::foo; + * they must be made in the global namespace + * (so that specializations appear in the xo::reflect namespace) + * + * Finally to permanently capture struct ingredients, + * this statement must run at least once: + * + * xo::reflect::reflect_struct(); + * + **/ + +#define REFLECT_BASE_STRUCT_INFO_TBODY(qualified_struct_name, member_count) \ + struct reflect_struct_traits { \ + static constexpr std::size_t n_members = member_count; \ + static constexpr std::size_t n_parents = 0; \ + } + +#define REFLECT_BASE_STRUCT_INFO(qualified_struct_name, member_count) \ + template<> \ + REFLECT_BASE_STRUCT_INFO_TBODY(qualified_struct_name, member_count) + +#define REFLECT_STRUCT_MEMBER_INFO_TBODY(qualified_struct_name, member_ix, member_name) \ + struct reflect_struct_member { \ + constexpr auto get() const { \ + return reflect_struct_member_aux(xo::flatstring(#member_name), \ + &qualified_struct_name::member_name##_); \ + } \ + }; + +#define REFLECT_STRUCT_MEMBER_INFO(qualified_struct_name, member_ix, member_name) \ + template <> \ + REFLECT_STRUCT_MEMBER_INFO_TBODY(qualified_struct_name, member_ix, member_name) + + /** @class reflect_struct_traits + * + * @brief header-only ingredients for reflecting a struct + * + * A class/struct T to be reflected should provide specializations + * of + * - xo::reflect::reflect_struct_traits<> + * - xo::reflect::reflect_struct_member<> + * - xo::reflect::reflect_struct_parent<> [aspirational, not yet] + * + * For example: + * + * @code + * + * namespace foo { + * struct mycomplex { double real_, double imag_; }; + * } + * + * namespace xo { + * namespace reflect { + * template <> + * struct reflect_struct_traits { + * + * // number of member variables + * static constexpr std::size_t n_members = 2; + * + * // number of parent structs + * // (i.e. directly inherited structs) + * static constexpr std::size_t n_parents = 0; + * }; + * } + * } + * + * // or use convenience macro (must be in global namespace) + * REFLECT_BASE_STRUCT_INFO(foo::mycomplex, 2); + * + * @endcode + **/ + template + struct reflect_struct_traits; + + /** @class reflect_struct_member + * @brief reflection ingredients for a particular struct member + * + * A class/struct T to be reflected should provide specializations + * of + * - xo::reflect::reflect_struct_traits<> + * - xo::reflect::reflect_struct_member<> + * - xo::reflect::reflect_struct_parent<> [aspirational, not yet] + * + * For example: + * + * @code + * + * namespace foo { + * struct mycomplex { double real_, double imag_; }; + * } + * + * namespace xo { + * namespace reflect { + * // reflect foo::mycomplex::real_ + * template <> + * struct reflect_struct_member { + * constexpr auto operator()() const { return reflect_struct_member_aux(xo::flatstring("real"), &foo::mycomplex::real_); } + * }; + * + * // reflect foo::mycomplex::imag_ + * template <> + * struct reflect_struct_member { + * constexpr auto operator()() const { return reflect_struct_member_aux(xo::flatstring("imag"), &foo::mycomplex::imag_); } + * }; + * } + * } + * + * // or use convenience macro (must be invoked from global namespace) + * REFLECT_STRUCT_MEMBER_INFO(foo::mycomplex, 0, real); + * REFLECT_STRUCT_MEMBER_INFO(foo::mycomplex, 1, imag); + * + * @endcode + **/ + template + struct reflect_struct_member; + + // ---------------------------------------------------------------- + + template + struct reflect_struct_member_info { + //using struct_type = StructT; + using owner_type = OwnerT; + using member_type = MemberT; + using member_name_type = MemberNameT; + + constexpr reflect_struct_member_info(const MemberNameT & member_name, + MemberT OwnerT::* member_addr) + : member_name_{member_name}, member_addr_{member_addr} {} + + MemberNameT member_name_; + MemberT OwnerT::* member_addr_; + }; + + // ---------------------------------------------------------------- + + template + constexpr auto reflect_struct_member_aux(const MemberNameT & member_name, + MemberT OwnerT:: * member_addr) { + return reflect_struct_member_info + (member_name, member_addr); + }; + + } /*namespace reflect*/ +} /*namespace xo*/ + +/** end reflect_struct_info.hpp **/ From c7b37d35bb8e5a97131d3dcf908fbee0161201f9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 24 Jun 2024 23:57:57 -0400 Subject: [PATCH 1173/2524] xo-refcnt: naked pointer -> Borrow ctor --- README.md | 2 +- include/xo/refcnt/Refcounted.hpp | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 19a14a1d..e17529d9 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ $ xo-build --clone xo-refcnt or equivalently ``` -$ git clone git@github.com:rconybea/refcnt.git xo-refcnt +$ git clone git@github.com:Rconybea/refcnt.git xo-refcnt ``` ### build + install diff --git a/include/xo/refcnt/Refcounted.hpp b/include/xo/refcnt/Refcounted.hpp index b54d5b5e..da4ba539 100644 --- a/include/xo/refcnt/Refcounted.hpp +++ b/include/xo/refcnt/Refcounted.hpp @@ -257,9 +257,12 @@ namespace xo { template class Borrow { public: - template + template Borrow(rp const & x) : ptr_(x.get()) {} + template + Borrow(S * x) : ptr_(x) {} + Borrow(Borrow const & x) = default; /* convert from another borrow, if it has compatible pointer type */ From 0454fdcbff0e852ffd03fd83e53a32ff89fd9700 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 00:26:05 -0400 Subject: [PATCH 1174/2524] xo-reflect: minor: move assign_tdextra() impl to .hpp --- include/xo/reflect/TypeDescr.hpp | 5 ++++- src/reflect/TypeDescr.cpp | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index 31c58063..4591bbe2 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -351,7 +351,10 @@ namespace xo { /* call this once to attach extended type information to a type-description * (e.g. description of struct members for a record type) */ - void assign_tdextra(std::unique_ptr tdx); + void assign_tdextra(std::unique_ptr tdx) { + this->complete_flag_ = true; + this->tdextra_ = std::move(tdx); + } private: TypeDescrBase(TypeId id, diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp index 41f5204e..9bb4bc39 100644 --- a/src/reflect/TypeDescr.cpp +++ b/src/reflect/TypeDescr.cpp @@ -288,6 +288,7 @@ namespace xo { return retval; } /*mark_complete*/ +#ifdef NOT_USING void TypeDescrBase::assign_tdextra(std::unique_ptr tdx) { @@ -302,6 +303,7 @@ namespace xo { this->complete_flag_ = true; this->tdextra_ = std::move(tdx); } /*assign_tdextra*/ +#endif TypeDescrTable::TypeDescrTable() { Reflect::require(); From a7b6ca28645b1e4f845a0d2a650991ba61805ead Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 00:26:25 -0400 Subject: [PATCH 1175/2524] xo-reflect: + xo-reflectutil in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 370cbafb..4e43dfd8 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Installs a few cmake ingredients, along with a build assistant `xo-build` for X $ xo-build --clone --configure --build --install xo-indentlog $ xo-build --clone --configure --build --install xo-refnct $ xo-build --clone --configure --build --install xo-subsys +$ xo-build --clone --configure --build --install xo-reflectutil ``` Note: can use `-n` to dry-run here From e1d8d7619b577f3e7335b93136973d7ff2314e77 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 00:27:36 -0400 Subject: [PATCH 1176/2524] xo-jit: ignore unused-parameter in llvm .h files --- include/xo/jit/IrPipeline.hpp | 47 +++++++++++++++------------- include/xo/jit/Jit.hpp | 29 +++++++++-------- include/xo/jit/LlvmContext.hpp | 7 ++++- include/xo/jit/activation_record.hpp | 7 +++-- 4 files changed, 52 insertions(+), 38 deletions(-) diff --git a/include/xo/jit/IrPipeline.hpp b/include/xo/jit/IrPipeline.hpp index 60473bc5..d8234e6b 100644 --- a/include/xo/jit/IrPipeline.hpp +++ b/include/xo/jit/IrPipeline.hpp @@ -9,28 +9,31 @@ #include "LlvmContext.hpp" /* stuff from kaleidoscope.cpp */ -#include "llvm/ADT/APFloat.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/IR/BasicBlock.h" -#include "llvm/IR/Constants.h" -#include "llvm/IR/DerivedTypes.h" -#include "llvm/IR/Function.h" -#include "llvm/IR/IRBuilder.h" -#include "llvm/IR/LLVMContext.h" -#include "llvm/IR/Module.h" -#include "llvm/IR/PassManager.h" -#include "llvm/IR/Type.h" -#include "llvm/IR/Verifier.h" -#include "llvm/Passes/PassBuilder.h" -#include "llvm/Passes/StandardInstrumentations.h" -#include "llvm/Support/TargetSelect.h" -#include "llvm/Target/TargetMachine.h" -#include "llvm/Transforms/InstCombine/InstCombine.h" -#include "llvm/Transforms/Scalar.h" -#include "llvm/Transforms/Scalar/GVN.h" -#include "llvm/Transforms/Utils/Mem2Reg.h" -#include "llvm/Transforms/Scalar/Reassociate.h" -#include "llvm/Transforms/Scalar/SimplifyCFG.h" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +# include "llvm/ADT/APFloat.h" +# include "llvm/ADT/STLExtras.h" +# include "llvm/IR/BasicBlock.h" +# include "llvm/IR/Constants.h" +# include "llvm/IR/DerivedTypes.h" +# include "llvm/IR/Function.h" +# include "llvm/IR/IRBuilder.h" +# include "llvm/IR/LLVMContext.h" +# include "llvm/IR/Module.h" +# include "llvm/IR/PassManager.h" +# include "llvm/IR/Type.h" +# include "llvm/IR/Verifier.h" +# include "llvm/Passes/PassBuilder.h" +# include "llvm/Passes/StandardInstrumentations.h" +# include "llvm/Support/TargetSelect.h" +# include "llvm/Target/TargetMachine.h" +# include "llvm/Transforms/InstCombine/InstCombine.h" +# include "llvm/Transforms/Scalar.h" +# include "llvm/Transforms/Scalar/GVN.h" +# include "llvm/Transforms/Utils/Mem2Reg.h" +# include "llvm/Transforms/Scalar/Reassociate.h" +# include "llvm/Transforms/Scalar/SimplifyCFG.h" +#pragma GCC diagnostic pop //#include diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 5af092d0..98fbc614 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -4,19 +4,22 @@ #pragma once -#include "llvm/ADT/StringRef.h" -#include "llvm/ExecutionEngine/JITSymbol.h" -#include "llvm/ExecutionEngine/Orc/CompileUtils.h" -#include "llvm/ExecutionEngine/Orc/Core.h" -#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" -#include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h" -#include "llvm/ExecutionEngine/Orc/IRCompileLayer.h" -#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h" -#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" -#include "llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h" -#include "llvm/ExecutionEngine/SectionMemoryManager.h" -#include "llvm/IR/DataLayout.h" -#include "llvm/IR/LLVMContext.h" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +# include "llvm/ADT/StringRef.h" +# include "llvm/ExecutionEngine/JITSymbol.h" +# include "llvm/ExecutionEngine/Orc/CompileUtils.h" +# include "llvm/ExecutionEngine/Orc/Core.h" +# include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" +# include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h" +# include "llvm/ExecutionEngine/Orc/IRCompileLayer.h" +# include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h" +# include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" +# include "llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h" +# include "llvm/ExecutionEngine/SectionMemoryManager.h" +# include "llvm/IR/DataLayout.h" +# include "llvm/IR/LLVMContext.h" +#pragma GCC diagnostic pop #include namespace xo { diff --git a/include/xo/jit/LlvmContext.hpp b/include/xo/jit/LlvmContext.hpp index 38f1492a..cf594c82 100644 --- a/include/xo/jit/LlvmContext.hpp +++ b/include/xo/jit/LlvmContext.hpp @@ -6,7 +6,12 @@ #pragma once #include "xo/refcnt/Refcounted.hpp" -#include "llvm/IR/LLVMContext.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +# include "llvm/IR/LLVMContext.h" +#pragma GCC diagnostic pop + //#include namespace xo { diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index eb98975e..c2aba2dd 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -6,8 +6,11 @@ #pragma once #include "LlvmContext.hpp" -#include -#include +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +# include +# include +#pragma GCC diagnostic pop #include //#include From ce9d93240a98cc387dcfb2f955ffaa1b1484d7b6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 00:27:58 -0400 Subject: [PATCH 1177/2524] xo-jit: + LLVM_LIBRARY_DIR, need this on darwin --- src/jit/CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index fe0b9e3d..4168eebe 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -23,6 +23,15 @@ separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) message(STATUS "LLVM_DEFINITIONS_LIST=[${LLVM_DEFINITIONS_LIST}]") +# LLVM library directory +execute_process( + COMMAND llvm-config --libdir + COMMAND tr -d '\n' + OUTPUT_VARIABLE LLVM_LIBRARY_DIR +) + +message(STATUS "LLVM_LIBRARY_DIR=[${LLVM_LIBRARY_DIR}]") + # Find the libraries that correspond to the LLVM components execute_process( COMMAND llvm-config --libs all @@ -34,6 +43,7 @@ message(STATUS "LLVM_LIBS=[${LLVM_LIBS}]") target_include_directories(${SELF_LIB} PUBLIC ${LLVM_INCLUDE_DIRS}) target_compile_definitions(${SELF_LIB} PUBLIC ${LLVM_DEFINITIONS_LIST}) +target_link_directories(${SELF_LIB} PUBLIC ${LLVM_LIBRARY_DIR}) target_link_libraries(${SELF_LIB} PUBLIC ${LLVM_LIBS}) # end CMakeLists.txt From 72d0305cdbddd07ae937b7a88c668abafa89025c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 09:31:07 -0400 Subject: [PATCH 1178/2524] xo-jit: refactor: Jit::mangle() -> std::string_view --- include/xo/jit/Jit.hpp | 10 +++++++--- include/xo/jit/MachPipeline.hpp | 4 ++-- src/jit/MachPipeline.cpp | 26 ++++++++++++++------------ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 98fbc614..9888e1ca 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -133,8 +133,10 @@ namespace xo { /** intern @p symbol, binding it to address @p dest **/ template llvm::Error intern_symbol(const std::string & symbol, T * dest) { + auto mangled_sym = mangler_(symbol); + llvm::orc::SymbolMap symbol_map; - symbol_map[mangler_(symbol)] + symbol_map[mangled_sym] = llvm::orc::ExecutorSymbolDef(llvm::orc::ExecutorAddr::fromPtr(dest), llvm::JITSymbolFlags()); @@ -144,8 +146,10 @@ namespace xo { } /*intern_symbol*/ /** report mangled symbol name **/ - auto mangle(StringRef name) { - return this->mangler_(name.str()); + std::string_view mangle(StringRef name) { + auto tmp = *(this->mangler_(name.str())); + + return std::string_view(tmp.data(), tmp.size()); } llvm::Expected lookup(StringRef name) { diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 308397fc..ec7e0b4d 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -108,8 +108,8 @@ namespace xo { /** dump text description of module contents to console **/ void dump_current_module(); - /** report mangle symbol **/ - std::string mangle(const std::string & x) const; + /** report mangled symbol for @p x **/ + std::string_view mangle(const std::string & x) const; /** lookup symbol in jit-associated output library **/ llvm::Expected lookup_symbol(const std::string & x); diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 9a1a50bb..e647c63f 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -308,9 +308,11 @@ namespace xo { llvm::Function * MachPipeline::codegen_primitive(ref::brw expr) { - //constexpr bool c_debug_flag = true; + constexpr bool c_debug_flag = true; using xo::scope; + scope log(XO_DEBUG(c_debug_flag)); + /** note: documentation (such as it is) for llvm::Function here: * * https://llvm.org/doxygenL/classllvm_1_1Function.html @@ -359,8 +361,13 @@ namespace xo { if (expr->explicit_symbol_def()) { static llvm::ExitOnError llvm_exit_on_err; - llvm_exit_on_err(this->jit_->intern_symbol(expr->name(), - expr->function_address())); + auto name = expr->name(); + auto fn_addr = expr->function_address(); + + log && log(xtag("sym", name), + xtag("mangled_sym", this->jit_->mangle(name))); + + llvm_exit_on_err(this->jit_->intern_symbol(name, fn_addr)); #ifdef NOT_USING if (!llvm_result) { @@ -373,6 +380,8 @@ namespace xo { return nullptr; } #endif + } else { + log && log("not requiring absolute address", xtag("sym", expr->name())); } #ifdef OBSOLETE @@ -987,17 +996,10 @@ namespace xo { this->recreate_llvm_ir_pipeline(); } /*machgen_current_module*/ - std::string + std::string_view MachPipeline::mangle(const std::string & sym) const { - auto p = this->jit_->mangle(sym); - - if (p) - return (*p).str(); - - throw std::runtime_error(tostr("MachPipeline::mangle" - ": mangle(sym) returned empty pointer", - xtag("sym", sym))); + return this->jit_->mangle(sym); } /*mangle*/ llvm::Expected From fcd87b52c0ead72b6dff4332b16b5d4f18712928 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 09:35:17 -0400 Subject: [PATCH 1179/2524] xo-jit: + MachPipeline::data_layout() --- include/xo/jit/MachPipeline.hpp | 7 +++++-- src/jit/MachPipeline.cpp | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index ec7e0b4d..0dc5a6a8 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -57,6 +57,7 @@ namespace xo { using Expression = xo::ast::Expression; using Lambda = xo::ast::Lambda; using TypeDescr = xo::reflect::TypeDescr; + using DataLayout = llvm::DataLayout; //using ConstantInterface = xo::ast::ConstantInterface; public: @@ -64,7 +65,7 @@ namespace xo { static llvm::Expected> make_aux(); static xo::ref::rp make(); - // ----- module access ----- + // ----- access ----- llvm::Module * current_module() { return llvm_module_.get(); } ref::brw llvm_cx() { return llvm_cx_; } @@ -72,6 +73,8 @@ namespace xo { /** target triple = string describing target host for codegen **/ const std::string & target_triple() const; + /** data layout = rules for alignment/padding; specific to target host **/ + const DataLayout & data_layout() const; /** append function names defined in attached module to *p_v * * (RC 15jun2024 - this part is working) @@ -173,7 +176,7 @@ namespace xo { std::unique_ptr> llvm_toplevel_ir_builder_; /** a module (1:1 with library ?) being prepared by llvm. - * IR-level -- does not contain machine code + * IR-level -- does not contain machine coode * * - function names are unique within a module. **/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index e647c63f..5b3e2cba 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -17,6 +17,7 @@ namespace xo { using xo::reflect::Reflect; using xo::reflect::StructMember; using xo::reflect::TypeDescr; + using llvm::DataLayout; using std::cerr; using std::endl; @@ -106,6 +107,11 @@ namespace xo { return this->jit_->target_triple(); } + const DataLayout & + MachPipeline::data_layout() const { + return this->jit_->data_layout(); + } + std::vector MachPipeline::get_function_name_v() { std::vector retval; From 56210442a2ea0d9f22e8262309318d157412d6b6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 09:36:06 -0400 Subject: [PATCH 1180/2524] xo-jit: honor clang nit --- src/jit/MachPipeline.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 5b3e2cba..bf2d76e2 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -7,7 +7,7 @@ namespace xo { using xo::ast::exprtype; using xo::ast::Expression; using xo::ast::ConstantInterface; - using xo::ast::FunctionInterface; + //using xo::ast::FunctionInterface; using xo::ast::PrimitiveInterface; using xo::ast::Lambda; using xo::ast::Variable; From 360d1da2f95f525ff97f412e55bb854c4c88f641 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 09:36:39 -0400 Subject: [PATCH 1181/2524] xo-jit: + struct unit test ! --- utest/MachPipeline.test.cpp | 81 ++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp index c2c7f5bc..37fb581c 100644 --- a/utest/MachPipeline.test.cpp +++ b/utest/MachPipeline.test.cpp @@ -3,6 +3,7 @@ #include "xo/jit/MachPipeline.hpp" #include "xo/expression/Primitive.hpp" #include "xo/ratio/ratio.hpp" +#include "xo/ratio/ratio_reflect.hpp" #include "xo/reflect/reflect_struct.hpp" #include "xo/indentlog/scope.hpp" #include @@ -184,8 +185,32 @@ namespace xo { } } /*TEST_CASE(machpipeline)*/ + rp + make_ratio() { + auto make_ratio_impl = make_primitive("make_ratio_impl", + xo::ratio::make_ratio, + true /*explicit_symbol_def*/, + llvmintrinsic::invalid); + REQUIRE(make_ratio_impl.get()); + REQUIRE(make_ratio_impl->explicit_symbol_def()); + + /* jit-prepared library: + * 1. *uses* make_ratio_impl + * 2. *provides* make_ratio (can do jit->lookup_symbol("make_ratio")) + */ + auto n_var = make_var("n", Reflect::require()); + auto d_var = make_var("d", Reflect::require()); + auto call1 = make_apply(make_ratio_impl, {n_var, d_var}); /*make_ratio(n,d)*/ + + auto make_ratio = make_lambda("make_ratio", + {n_var, d_var}, + call1); + + return make_ratio; + } + TEST_CASE("machpipeline.struct", "[llvm][llvm_struct]") { - constexpr bool c_debug_flag = false; + constexpr bool c_debug_flag = true; // can get bits from /dev/random by uncommenting the 2nd line below //uint64_t seed = xxx; @@ -200,9 +225,61 @@ namespace xo { /* let's reflect xo::ratio::ratio */ - auto struct_td = reflect_struct>(); + using ratio_type = xo::ratio::ratio; + + auto struct_td = reflect_struct(); REQUIRE(struct_td); + + auto fn_ast = make_ratio(); + + llvm::Value * llvm_ircode = jit->codegen_toplevel(fn_ast); + + /* TODO: printer for llvm::Value* */ + if (llvm_ircode) { + /* note: llvm:errs() is 'raw stderr stream' */ + cerr << "llvm_ircode:" << endl; + llvm_ircode->print(llvm::errs()); + cerr << endl; + } else { + cerr << "code generation failed" + << xtag("fn_ast", fn_ast) + << endl; + } + + REQUIRE(llvm_ircode); + + jit->machgen_current_module(); + + cerr << "execution session after codegen:" << endl; + jit->dump_execution_session(); + + /** lookup compiled function pointer in jit **/ + auto llvm_addr = jit->lookup_symbol(fn_ast->name()); + + cerr << "execution session after lookup attempt:" << endl; + jit->dump_execution_session(); + + if (!llvm_addr) { + cerr << "ex2: lookup: symbol not found" + << xtag("symbol", fn_ast->name()) + << endl; + } else { + cerr << "ex2: lookup: symbol found" + << xtag("llvm_addr", llvm_addr.get().getValue()) + << xtag("symbol", fn_ast->name()) + << endl; + } + + auto fn_ptr = llvm_addr.get().toPtr(); + + REQUIRE(fn_ptr); + + auto value = (*fn_ptr)(2, 3); + + log && log(xtag("value.num", value.num()), + xtag("value.den", value.den())); + } /*TEST_CASE(machpipeline.struct)*/ } /*namespace ut*/ } /*namespace xo*/ From e8f8a43f7a5efe5853103626a5b92667a240b468 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 10:58:31 -0400 Subject: [PATCH 1182/2524] xo-jit: + docs/ directory + sphinx doc skeleton --- CMakeLists.txt | 3 ++ docs/CMakeLists.txt | 7 +++++ docs/README | 70 +++++++++++++++++++++++++++++++++++++++++++++ docs/conf.py | 39 +++++++++++++++++++++++++ docs/glossary.rst | 20 +++++++++++++ docs/index.rst | 21 ++++++++++++++ 6 files changed, 160 insertions(+) create mode 100644 docs/CMakeLists.txt create mode 100644 docs/README create mode 100644 docs/conf.py create mode 100644 docs/glossary.rst create mode 100644 docs/index.rst diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e1f3fb4..2ee6351c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,9 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets add_subdirectory(example) add_subdirectory(utest) +# reminder: must come last: docs targets depend on all the other library/utest targets +add_subdirectory(docs) + # ---------------------------------------------------------------- # docs targets depend on all the other library/utest targets # diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 00000000..bf9c8563 --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,7 @@ +# xo-jit/docs/CMakeLists.txt + +xo_doxygen_collect_deps() +xo_docdir_doxygen_config() +xo_docdir_sphinx_config( + index.rst glossary.rst +) diff --git a/docs/README b/docs/README new file mode 100644 index 00000000..2fab6399 --- /dev/null +++ b/docs/README @@ -0,0 +1,70 @@ +build + + +-----------------------------------------------+ + | cmake | + | CMakeLists.txt | + | $PREFIX/share/cmake/xo_macros/xo_cxx.cmake | + +-----------------------------------------------+ + | + | +----------------------+ + +------------------------------------------------->| .build/docs/Doxyfile | + | +----------------------+ + | | + | /------------/ + | | + | v + | +---------------------------------------+ +-----------------+ + +---->| doxygen |--->| .build/docs/dox | + | | $PREFIX/share/xo-macros/Doxyfile.in | | +- html/ | + | +---------------------------------------+ | +- xml/ | + | +-----------------+ + | | + | /------------/ + | | + | v + | +---------------------------------------+ +--------------------+ + \---->| sphinx |--->| .build/docs/sphinx | + | +- conf.py | | +- html/ | + | +- _static/ | +--------------------+ + | +- *.rst | + +---------------------------------------+ + +files + + README this file + CMakeLists.txt build entry point + conf.py sphinx config + _static static files for sphinx + +map + + index.rst + +- install.rst + +- examples.rst + +- unit-quantities.rst + +- classes.rst + +- glossary.rst + ... + +examples + +.. doxygenclass:: ${c++ class name} + :project: + :path: + :members: + :protected-members: + :private-members: + :undoc-members: + :member-groups: + :members-only: + :outline: + :no-link: + :allow-dot-graphs: + +.. doxygendefine:: ${c preprocessor define} + +.. doxygenconcept:: ${c++ concept definition} + +.. doxygenenum:: ${c++ enum definition} + +.. doxygenfunction:: ${c++ function name} diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..31acd35e --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,39 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'xo jit documentation' +copyright = '2024, Roland Conybeare' +author = 'Roland Conybeare' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +#extensions = [] +extensions = [ "breathe", + "sphinx.ext.mathjax", # inline math + "sphinx.ext.autodoc", # generate info from docstrings + "sphinxcontrib.ditaa", # diagrams-through-ascii-art + "sphinxcontrib.plantuml" # text -> uml diagrams + ] + +# note: breathe requires doxygen xml output -> must have GENERATE_XML = YES in Doxyfile.in +# match project name in Doxyfile.in +breathe_default_project = "xodoxxml" + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +pygments_style = 'sphinx' + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +#html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] +html_favicon = '_static/img/favicon.ico' diff --git a/docs/glossary.rst b/docs/glossary.rst new file mode 100644 index 00000000..c5a22ec1 --- /dev/null +++ b/docs/glossary.rst @@ -0,0 +1,20 @@ +.. _glossary: + +Glossary +-------- + +.. glossary:: + xsession + | Shorthand for `llvm::orc::ExecutionSession`. + | Manages running JIT-generated machine code in the host process + + td + | Short for `xo::reflect::TypeDescr`. + + XO + A set of integrated c++ libraries for complex event processing, with browser and python integration. + `xo documentation here`_ + +.. _xo documentation here: https://rconybea.github.io/web/sw/xo.html + +.. toctree:: diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..ea337854 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,21 @@ +.. xo-jit documentation master file, created by + sphinx-quickstart on Wed Mar 6 23:32:27 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +xo-jit documentation +==================== + +xo-jit compiles xo-expression AST's to runnable-in-this-process machine code. + +* uses C++ reflection to simplify making c++ type-equivalents available in LLVM +* uses C++ reflection to simplify making c++ functions available in LLVM +* integration with python (see sister project xo-pyjit) + +.. toctree:: + :maxdepth: 2 + :caption: xo-jit contents + + glossary + genindex + search From 274370c28c0c51b41872c651ff30f60ef5961142 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 11:04:58 -0400 Subject: [PATCH 1183/2524] xo-jit: + Jit::xsession accessor --- include/xo/jit/Jit.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 9888e1ca..f983fb2e 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -109,13 +109,15 @@ namespace xo { std::move(*data_layout)); } + /* exposing this for printing */ + const ExecutionSession * xsession() const { return xsession_.get(); } + const DataLayout & data_layout() const { return data_layout_; } + + JITDylib & dest_dynamic_lib_ref() { return dest_dynamic_lib_; } const std::string & target_triple() const { return xsession_->getTargetTriple().getTriple(); } - const DataLayout & data_layout() const { return data_layout_; } - - JITDylib & dest_dynamic_lib_ref() { return dest_dynamic_lib_; } /** compile module to machine code that's runnable from this process; * incorporate into @ref dest_dynamic_lib_ From 09d884737da7f0edd2cf0244765cd234954d25b8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 11:13:25 -0400 Subject: [PATCH 1184/2524] xo-jit: + MachPipeline::codegen_type() --- include/xo/jit/MachPipeline.hpp | 17 +++++++++++++++++ src/jit/MachPipeline.cpp | 5 +++++ 2 files changed, 22 insertions(+) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 0dc5a6a8..49be235d 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -86,6 +86,23 @@ namespace xo { // ----- code generation ----- + /** establish llvm IR corresponding to a c++ type. + * Handles + * T := bool|char|short|int|long|float|double + * | T1(*)(T2..Tn) + * | struct{T1,..,Tn} + * + * Not supported yet: + * - vector + * - string + * - map + * - unions + * - pointers (except function pointers) + * + * Idempotent: multiple calls with the same @p td produce the same @c llvm::Type pointer. + * @c llvm::Type instances are *immortal* (llvm interns them into opaque global lookup tables) + **/ + llvm::Type * codegen_type(TypeDescr td); llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); llvm::Value * codegen_apply(ref::brw expr, llvm::IRBuilder<> & ir_builder); diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index bf2d76e2..88037393 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -311,6 +311,11 @@ namespace xo { } } + llvm::Type * + MachPipeline::codegen_type(TypeDescr td) { + return td_to_llvm_type(llvm_cx_.borrow(), td); + } + llvm::Function * MachPipeline::codegen_primitive(ref::brw expr) { From 585e4cc35c9d70a6f77c37e507792686edc48815 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 11:14:06 -0400 Subject: [PATCH 1185/2524] xo-jit: + MachPipeline::xsession() --- include/xo/jit/MachPipeline.hpp | 4 ++++ src/jit/MachPipeline.cpp | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 49be235d..3e475e92 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -43,6 +43,7 @@ #include "llvm/Transforms/Scalar/GVN.h" #include "llvm/Transforms/Scalar/Reassociate.h" #include "llvm/Transforms/Scalar/SimplifyCFG.h" +#include namespace xo { @@ -57,6 +58,7 @@ namespace xo { using Expression = xo::ast::Expression; using Lambda = xo::ast::Lambda; using TypeDescr = xo::reflect::TypeDescr; + using ExecutionSession = llvm::orc::ExecutionSession; using DataLayout = llvm::DataLayout; //using ConstantInterface = xo::ast::ConstantInterface; @@ -73,6 +75,8 @@ namespace xo { /** target triple = string describing target host for codegen **/ const std::string & target_triple() const; + /** execution session (run jit-generated machine code in this process) **/ + const ExecutionSession * xsession() const; /** data layout = rules for alignment/padding; specific to target host **/ const DataLayout & data_layout() const; /** append function names defined in attached module to *p_v diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 88037393..5f13c371 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -17,6 +17,7 @@ namespace xo { using xo::reflect::Reflect; using xo::reflect::StructMember; using xo::reflect::TypeDescr; + using llvm::orc::ExecutionSession; using llvm::DataLayout; using std::cerr; using std::endl; @@ -96,6 +97,11 @@ namespace xo { ir_pipeline_ = new IrPipeline(llvm_cx_); } /*recreate_llvm_ir_pipeline*/ + const ExecutionSession * + MachPipeline::xsession() const { + return this->jit_->xsession(); + } + /** identifies target host/architecture for machine code. * e.g. "x86_64-unknown-linux-gnu" **/ From 7e1f3c8cb502919aa36216139a3f54dccb86be12 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 11:14:22 -0400 Subject: [PATCH 1186/2524] xo-jit: cosmetic -- code layout --- src/jit/MachPipeline.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 5f13c371..5228cc09 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -97,6 +97,11 @@ namespace xo { ir_pipeline_ = new IrPipeline(llvm_cx_); } /*recreate_llvm_ir_pipeline*/ + const DataLayout & + MachPipeline::data_layout() const { + return this->jit_->data_layout(); + } + const ExecutionSession * MachPipeline::xsession() const { return this->jit_->xsession(); @@ -113,11 +118,6 @@ namespace xo { return this->jit_->target_triple(); } - const DataLayout & - MachPipeline::data_layout() const { - return this->jit_->data_layout(); - } - std::vector MachPipeline::get_function_name_v() { std::vector retval; @@ -242,7 +242,7 @@ namespace xo { llvm_membertype_v.push_back(td_to_llvm_type(llvm_cx, sm.get_member_td())); - } + } std::string struct_name = std::string(struct_td->short_name()); From cf95f64961f675eeb3a1282aa6997b71f12a4a0e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 11:14:41 -0400 Subject: [PATCH 1187/2524] xo-jit: modest output change in utest --- utest/MachPipeline.test.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp index 37fb581c..7a309f74 100644 --- a/utest/MachPipeline.test.cpp +++ b/utest/MachPipeline.test.cpp @@ -251,13 +251,14 @@ namespace xo { jit->machgen_current_module(); - cerr << "execution session after codegen:" << endl; + log && log("execution session after codegen:"); + //log && log(jit->xsession()); // segfaults jit->dump_execution_session(); /** lookup compiled function pointer in jit **/ auto llvm_addr = jit->lookup_symbol(fn_ast->name()); - cerr << "execution session after lookup attempt:" << endl; + log && log("execution session after lookup attempt:"); jit->dump_execution_session(); if (!llvm_addr) { From 42331ce8bab4acb7af802292add90543f26e7855 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 18:39:37 -0400 Subject: [PATCH 1188/2524] xo-reflect: multiple improvements -- kitchen sink --- include/xo/reflect/StructReflector.hpp | 277 +++++++-------- include/xo/reflect/TypeDescr.hpp | 1 + include/xo/reflect/TypeDescrExtra.hpp | 5 + include/xo/reflect/atomic/AtomicTdx.hpp | 41 +-- include/xo/reflect/function/FunctionTdx.hpp | 1 + include/xo/reflect/reflect_struct.hpp | 71 ++++ include/xo/reflect/struct/StructMember.hpp | 360 ++++++++++---------- include/xo/reflect/struct/StructTdx.hpp | 2 + include/xo/reflect/vector/VectorTdx.hpp | 19 +- src/reflect/TypeDescrExtra.cpp | 4 +- src/reflect/atomic/AtomicTdx.cpp | 1 + src/reflect/struct/StructTdx.cpp | 76 ++--- 12 files changed, 479 insertions(+), 379 deletions(-) create mode 100644 include/xo/reflect/reflect_struct.hpp diff --git a/include/xo/reflect/StructReflector.hpp b/include/xo/reflect/StructReflector.hpp index 750b8669..220990a5 100644 --- a/include/xo/reflect/StructReflector.hpp +++ b/include/xo/reflect/StructReflector.hpp @@ -9,153 +9,154 @@ #include namespace xo { - namespace reflect { - template - class SelfTagger {}; + namespace reflect { + template + class SelfTagger {}; - template - struct SelfTagger { - static TaggedPtr self_tp(void * object) { - return (reinterpret_cast(object))->self_tp(); - } - }; + template + struct SelfTagger { + static TaggedPtr self_tp(void * object) { + return (reinterpret_cast(object))->self_tp(); + } + }; - template - struct SelfTagger { - static TaggedPtr self_tp(void * /*object*/) { assert(false); return TaggedPtr::universal_null(); } - }; + template + struct SelfTagger { + static TaggedPtr self_tp(void * /*object*/) { assert(false); return TaggedPtr::universal_null(); } + }; - /* RAII pattern for reflecting a struct. + /* RAII pattern for reflecting a struct. + * + * Use: + * struct Foo { int x_; double y_; }; + * + * StructReflector sr; + * REFLECT_LITERAL_MEMBER(sr, x_); + * REFLECT_LITERAL_MEMBER(sr, y_); + * + * // optional: regardless, reflection will be completed when sr goes out of scope + * sr.require_complete(); + */ + template + class StructReflector { + public: + using struct_t = StructT; + + public: + StructReflector() : td_{EstablishTypeDescr::establish()} {} + ~StructReflector() { + this->require_complete(); + } + + bool is_complete() const { return s_reflected_flag; } + bool is_incomplete() const { return !s_reflected_flag; } + TypeDescr td() const { return td_; } + + template + void reflect_member(std::string const & member_name, + MemberT OwnerT::* member_addr) { + + auto accessor + (GeneralStructMemberAccessor::make(member_addr)); + + /* used to do this in GeneralStructMemberAccessor<> ctor, + * but that introduces #include cycle + */ + Reflect::require(); + + this->member_v_.emplace_back(member_name, std::move(accessor)); + } /*reflect_member*/ + + void require_complete() { + if(!s_reflected_flag) { + s_reflected_flag = true; + + constexpr bool have_to_self_tp = std::is_base_of_v; + + /* if self-tagging, can use .self_tp() to get most-derived tagged pointer */ + auto to_self_tp_fn + = ([](void * object) + { + return SelfTagger::self_tp(object); + }); + + auto tdx = StructTdx::make(std::move(this->member_v_), + have_to_self_tp, + to_self_tp_fn); + + this->td_->assign_tdextra(std::move(tdx)); + } + } /*complete*/ + + template + void adopt_ancestors() { + assert(Reflect::is_reflected()); + + TypeDescr ancestor_td = Reflect::require(); + + /* requires that reflection of AncestorT has completed */ + { + assert(ancestor_td->is_struct()); + assert(ancestor_td->complete_flag()); + } + + /* for structs, + * we know that object argument to TypeDescr::n_child() is unused + */ + for (uint32_t i = 0, n = ancestor_td->n_child(nullptr); i < n; ++i) { + StructMember const & member = ancestor_td->struct_member(i); + + this->member_v_.push_back(member.for_descendant()); + } + } /*adopt_ancestors*/ + + private: + /* set irrevocably to true when .complete() runs. + * + * want to reflect a particular type once; + * short-circuit 2nd or later attempts on the same type + */ + static bool s_reflected_flag; + + /* type description object for StructT */ + TypeDescrW td_; + + /* members of StructT (at least those we're choosing to reflect) */ + std::vector member_v_; + }; /*StructReflector*/ + + template + bool StructReflector::s_reflected_flag = false; + } /*namespace reflect*/ + + /* e.g. + * struct Foo { int bar_; }; + * struct Bar : public Foo { .. }; * - * Use: - * struct Foo { int x_; double y_; }; - * - * StructReflector sr; - * REFLECT_LITERAL_MEMBER(sr, x_); - * REFLECT_LITERAL_MEMBER(sr, y_); - * - * // optional: regardless, reflection will be completed when sr goes out of scope - * sr.require_complete(); + * StructReflector sr; + * REFLECT_EXPLICIT_MEMBER(sr, "bar", &Foo::bar_); */ - template - class StructReflector { - public: - using struct_t = StructT; - - public: - StructReflector() : td_{EstablishTypeDescr::establish()} {} - ~StructReflector() { - this->require_complete(); - } - - bool is_complete() const { return s_reflected_flag; } - bool is_incomplete() const { return !s_reflected_flag; } - - template - void reflect_member(std::string const & member_name, - MemberT OwnerT::* member_addr) { - - auto accessor - (GeneralStructMemberAccessor::make(member_addr)); - - /* used to do this in GeneralStructMemberAccessor<> ctor, - * but that introduces #include cycle - */ - Reflect::require(); - - this->member_v_.emplace_back(member_name, std::move(accessor)); - } /*reflect_member*/ - - void require_complete() { - if(!s_reflected_flag) { - s_reflected_flag = true; - - constexpr bool have_to_self_tp = std::is_base_of_v; - - /* if self-tagging, can use .self_tp() to get most-derived tagged pointer */ - auto to_self_tp_fn - = ([](void * object) - { - return SelfTagger::self_tp(object); - }); - - auto tdx = StructTdx::make(std::move(this->member_v_), - have_to_self_tp, - to_self_tp_fn); - - this->td_->assign_tdextra(std::move(tdx)); - } - } /*complete*/ - - template - void adopt_ancestors() { - assert(Reflect::is_reflected()); - - TypeDescr ancestor_td = Reflect::require(); - - /* requires that reflection of AncestorT has completed */ - { - assert(ancestor_td->is_struct()); - assert(ancestor_td->complete_flag()); - } - - /* for structs, - * we know that object argument to TypeDescr::n_child() is unused - */ - for (uint32_t i = 0, n = ancestor_td->n_child(nullptr); i < n; ++i) { - StructMember const & member = ancestor_td->struct_member(i); - - this->member_v_.push_back(member.for_descendant()); - } - } /*adopt_ancestors*/ - - private: - /* set irrevocably to true when .complete() runs. - * - * want to reflect a particular type once; - * short-circuit 2nd or later attempts on the same type - */ - static bool s_reflected_flag; - - /* type description object for StructT */ - TypeDescrW td_; - - /* members of StructT (at least those we're choosing to reflect) */ - std::vector member_v_; - }; /*StructReflector*/ - - template - bool StructReflector::s_reflected_flag = false; - } /*namespace reflect*/ - - /* e.g. - * struct Foo { int bar_; }; - * struct Bar : public Foo { .. }; - * - * StructReflector sr; - * REFLECT_EXPLICIT_MEMBER(sr, "bar", &Foo::bar_); - */ #define REFLECT_EXPLICIT_MEMBER(sr, member_name, member) sr.reflect_member(member_name, member) - /* e.g. - * struct Foo { int bar_; }; - * - * StructReflector sr; - * REFLECT_LITERAL_MEMBER(sr, bar_); - * - * then REFLECT_LITERAL_MEMBER() expands to something like: - * sr.reflect_member("bar_", &StructReflector::struct_t::bar_) - */ + /* e.g. + * struct Foo { int bar_; }; + * + * StructReflector sr; + * REFLECT_LITERAL_MEMBER(sr, bar_); + * + * then REFLECT_LITERAL_MEMBER() expands to something like: + * sr.reflect_member("bar_", &StructReflector::struct_t::bar_) + */ #define REFLECT_LITERAL_MEMBER(sr, member_name) sr.reflect_member(#member_name, &decltype(sr)::struct_t::member_name) - /* like REFLECT_LITERAL_MEMBER(), but append trailing underscore - * - * minor convenience, so we can write - * struct Foo { int bar_; }; - * - * StructReflector sr; - * REFLECT_MEMBER(sr, bar); // reflects Foo::bar_ as "bar" - */ + /* like REFLECT_LITERAL_MEMBER(), but append trailing underscore + * + * minor convenience, so we can write + * struct Foo { int bar_; }; + * + * StructReflector sr; + * REFLECT_MEMBER(sr, bar); // reflects Foo::bar_ as "bar" + */ #define REFLECT_MEMBER(sr, member_name) sr.reflect_member(#member_name, &decltype(sr)::struct_t::member_name##_) } /*namespace xo*/ diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index 31c58063..034f9e1c 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -305,6 +305,7 @@ namespace xo { * .n_child() reports #of instance variables (that have been reflected) */ uint32_t n_child(void * object) const { return this->tdextra_->n_child(object); } + /** in some circumstances can use this with object=nullptr **/ TaggedPtr child_tp(uint32_t i, void * object) const; /* require: diff --git a/include/xo/reflect/TypeDescrExtra.hpp b/include/xo/reflect/TypeDescrExtra.hpp index bef9463e..68291654 100644 --- a/include/xo/reflect/TypeDescrExtra.hpp +++ b/include/xo/reflect/TypeDescrExtra.hpp @@ -54,6 +54,11 @@ namespace xo { */ virtual TaggedPtr most_derived_self_tp(TypeDescrBase const * object_td, void * object) const; virtual uint32_t n_child(void * object) const = 0; + /** number of children, fixed at compile time. + * Will return 0 for types like std::vector<..> (because number is unknown); + * Will also return 0 for types like {bool, int, long} (because number is zero) + **/ + virtual uint32_t n_child_fixed() const = 0; virtual TaggedPtr child_tp(uint32_t i, void * object) const = 0; /* require: * .is_struct() diff --git a/include/xo/reflect/atomic/AtomicTdx.hpp b/include/xo/reflect/atomic/AtomicTdx.hpp index 98e10cfe..9bd5e567 100644 --- a/include/xo/reflect/atomic/AtomicTdx.hpp +++ b/include/xo/reflect/atomic/AtomicTdx.hpp @@ -7,31 +7,32 @@ #include namespace xo { - namespace reflect { - class TaggedPtr; + 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; + /* Extra type-associated information for an atomic type. + * We use this as degenerate catch-all case for types that aren't known + * to have additional structure (std::vector, std::map, int*, etc.) + */ + class AtomicTdx : public TypeDescrExtra { + public: + virtual ~AtomicTdx() = default; - static std::unique_ptr make(); + static std::unique_ptr make(); - // ----- Inherited from TypeDescrExtra ----- + // ----- 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; } + virtual Metatype metatype() const override { return Metatype::mt_atomic; } + virtual uint32_t n_child(void * /*object*/) const override { return 0; } + virtual uint32_t n_child_fixed() const override { return 0; } + virtual TaggedPtr child_tp(uint32_t /*i*/, void * /*object*/) const override; + virtual 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*/ + private: + AtomicTdx() = default; + }; /*TypeDescrExtra*/ + } /*namespace reflect*/ } /*namespace xo*/ /* end AtomicTdx.hpp */ diff --git a/include/xo/reflect/function/FunctionTdx.hpp b/include/xo/reflect/function/FunctionTdx.hpp index 5d2f57c2..64b02da3 100644 --- a/include/xo/reflect/function/FunctionTdx.hpp +++ b/include/xo/reflect/function/FunctionTdx.hpp @@ -33,6 +33,7 @@ namespace xo { virtual Metatype metatype() const override { return Metatype::mt_function; } virtual uint32_t n_child(void * /*object*/) const override { return 0; } + virtual uint32_t n_child_fixed() const override { return 0; } virtual TaggedPtr child_tp(uint32_t i, void * object) const override; const std::string & struct_member_name(uint32_t i) const override; diff --git a/include/xo/reflect/reflect_struct.hpp b/include/xo/reflect/reflect_struct.hpp new file mode 100644 index 00000000..8c0c843a --- /dev/null +++ b/include/xo/reflect/reflect_struct.hpp @@ -0,0 +1,71 @@ +/** @file reflect_struct.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "StructReflector.hpp" +#include "xo/reflectutil/reflect_struct_info.hpp" + +namespace xo { + namespace reflect { + namespace detail { + /** + * @pre reflect_struct_member will separately + * have been specialized for T. + * See discussion in [reflect_struct_info.hpp] + * + * + **/ + template + struct sr_member_helper { + /** reflect members starting from member with index number @tparam MemberIx + * + * @pre Members [0,..,MemberIx-1] must be already represented in @p *p_sr + **/ + static void add_members_from(StructReflector * p_sr) { + const auto & member_info + = reflect_struct_member().get(); + + p_sr->reflect_member(member_info.member_name_.c_str(), + member_info.member_addr_); + + /** reflect remaining members **/ + sr_member_helper::add_members_from(p_sr); + } + }; + + template + struct sr_member_helper { + /** base case -- all members have been refleccted **/ + static void add_members_from(StructReflector *) {} + }; + } /*namespace detail*/ + + + /** It's awkward to have Reflect::reflect<>() do the right thing, + * because there's no way to specialize on whether a type T is a struct. + * + * Use + * xo::reflect::Reflect::reflect_struct() instead + **/ + template + TypeDescr reflect_struct() { + StructReflector sr; + + if (sr.is_incomplete()) + detail::sr_member_helper::n_members>::add_members_from(&sr); + + /* TODO: handle composition: where T inherits another reflected type */ + /* TODO: handle multiple inheritance **/ + + return sr.td(); + } + } /*namespace reflect*/ +} /*namespace xo*/ + + +/** end reflect_struct.hpp **/ diff --git a/include/xo/reflect/struct/StructMember.hpp b/include/xo/reflect/struct/StructMember.hpp index 3c7bab62..e7b76413 100644 --- a/include/xo/reflect/struct/StructMember.hpp +++ b/include/xo/reflect/struct/StructMember.hpp @@ -9,227 +9,227 @@ #include namespace xo { -namespace reflect { - class AbstractStructMemberAccessor { - public: - virtual ~AbstractStructMemberAccessor() = default; + 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 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 struct + * containing this member. useful for consistency checking. + */ + virtual TypeDescr struct_td() const = 0; - /* get type-description object for this member - * e.g. if this member represents Foo::bar_ in - * struct Foo { int bar_; }; - * then - * .member_td() => Reflect::require(); - */ - virtual TypeDescr member_td() const = 0; + /* get type-description object for this member + * e.g. if this member represents Foo::bar_ in + * struct Foo { int bar_; }; + * then + * .member_td() => Reflect::require(); + */ + virtual TypeDescr member_td() const = 0; - /* get address of a particular member, given parent address */ - virtual void * address(void * struct_addr) const = 0; + /* get address of a particular member, given parent address */ + virtual void * address(void * struct_addr) const = 0; - virtual std::unique_ptr clone() const = 0; - }; /*AbstractStructMemberAccessor*/ + virtual std::unique_ptr clone() const = 0; + }; /*AbstractStructMemberAccessor*/ - /* GeneralStructMemberAccessor - * - * Use this to handle access to possibly-inherited struct members: - * - * struct Foo { int x_; } - * struct Bar { char * y_; } - * struct Quux : public Foo, public Bar { bool z_; } - * - * want to be able to access Bar::y from a Quux instance. - * in example, would use GenericStructMemberAccessor<> - * with: - * StructT = Quux, - * OwnerT = Bar, - * MemberT = char* - * - * Require: - * StructT* is assignable to OwnerT* (because StructT --isa--> OwnerT) - */ - template - class GeneralStructMemberAccessor : public AbstractStructMemberAccessor { - public: - /* pointer to a OwnerT member of type MemberT */ - using Memptr = MemberT OwnerT::*; + /* GeneralStructMemberAccessor + * + * Use this to handle access to possibly-inherited struct members: + * + * struct Foo { int x_; } + * struct Bar { char * y_; } + * struct Quux : public Foo, public Bar { bool z_; } + * + * want to be able to access Bar::y from a Quux instance. + * in example, would use GenericStructMemberAccessor<> + * with: + * StructT = Quux, + * OwnerT = Bar, + * MemberT = char* + * + * Require: + * StructT* is assignable to OwnerT* (because StructT --isa--> OwnerT) + */ + template + class GeneralStructMemberAccessor : public AbstractStructMemberAccessor { + public: + /* pointer to a OwnerT member of type MemberT */ + using Memptr = MemberT OwnerT::*; - public: - GeneralStructMemberAccessor(Memptr memptr) : member_td_{EstablishTypeDescr::establish()}, - memptr_{memptr} {} - GeneralStructMemberAccessor(GeneralStructMemberAccessor const & x) = default; - virtual ~GeneralStructMemberAccessor() = default; + public: + GeneralStructMemberAccessor(Memptr memptr) : member_td_{EstablishTypeDescr::establish()}, + memptr_{memptr} {} + GeneralStructMemberAccessor(GeneralStructMemberAccessor const & x) = default; + virtual ~GeneralStructMemberAccessor() = default; - static std::unique_ptr make(Memptr memptr) { - return std::unique_ptr(new GeneralStructMemberAccessor(memptr)); } + static std::unique_ptr make(Memptr memptr) { + return std::unique_ptr(new GeneralStructMemberAccessor(memptr)); } - /* get member address given address of parent struct - * (i.e. from Struct*, not from OwnerT*) - */ - MemberT * address_impl(StructT * self_addr) const { - OwnerT * owner_addr = self_addr; + /* 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*/ + return &(owner_addr->*memptr_); + } /*address_impl*/ - // ----- Inherited from AbstractStructMemberAccessor ----- + // ----- 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 - */ + 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*/ + return this->member_td_->most_derived_self_tp(this->address(struct_addr)); + //return TaggedPtr(this->member_td_, this->address(struct_addr)); + } /*member_tp*/ #endif - virtual TypeDescr struct_td() const override { return EstablishTypeDescr::establish(); } + virtual TypeDescr struct_td() const override { return EstablishTypeDescr::establish(); } - virtual TypeDescr member_td() const override { return this->member_td_; } + virtual TypeDescr member_td() const override { return this->member_td_; } - virtual void * address(void * struct_addr) const override { - return this->address_impl(reinterpret_cast(struct_addr)); - } /*address*/ + virtual void * address(void * struct_addr) const override { + return this->address_impl(reinterpret_cast(struct_addr)); + } /*address*/ - virtual std::unique_ptr clone() const override { - return std::unique_ptr - (new GeneralStructMemberAccessor(*this)); - } /*clone*/ + virtual std::unique_ptr clone() const override { + return std::unique_ptr + (new GeneralStructMemberAccessor(*this)); + } /*clone*/ - private: - /* type description for MemberT; .memptr is pointer-to-member-of-OwnerT, - * where that member has type MemberT - */ - TypeDescr member_td_ = nullptr; - /* pointer to member of OwnerT */ - Memptr memptr_ = nullptr; - }; /*GeneralStructMemberAccessor*/ + private: + /* type description for MemberT; .memptr is pointer-to-member-of-OwnerT, + * where that member has type MemberT + */ + TypeDescr member_td_ = nullptr; + /* pointer to member of OwnerT */ + Memptr memptr_ = nullptr; + }; /*GeneralStructMemberAccessor*/ - /* struct-member accessor via delegation, - * to accessor of a parent (or some other ancestor) class. - * - * struct Foo { int x_; } - * struct Bar { char * y_; } - * - * auto bar_x_access = GeneralStructMemberAccessor::make(&Foo::x_); - * - * or equivalently: - * auto foo_x_access = GeneralStructMemberAccessor::make(&Foo::x_); - * auto bar_x_access = AncestorStructMemberAccessor::adopt(foo_x_access); - * - * can use the 2nd form to adopt accessors from an already-reflected ancestor class - * - * Require: - * - StructT -isa-> AncestorT - */ - template - class AncestorStructMemberAccessor : public AbstractStructMemberAccessor { - public: - AncestorStructMemberAccessor(std::unique_ptr ancestor_accessor) - : ancestor_accessor_{std::move(ancestor_accessor)} {} - AncestorStructMemberAccessor(AncestorStructMemberAccessor const & x) = default; - virtual ~AncestorStructMemberAccessor() = default; + /* struct-member accessor via delegation, + * to accessor of a parent (or some other ancestor) class. + * + * struct Foo { int x_; } + * struct Bar { char * y_; } + * + * auto bar_x_access = GeneralStructMemberAccessor::make(&Foo::x_); + * + * or equivalently: + * auto foo_x_access = GeneralStructMemberAccessor::make(&Foo::x_); + * auto bar_x_access = AncestorStructMemberAccessor::adopt(foo_x_access); + * + * can use the 2nd form to adopt accessors from an already-reflected ancestor class + * + * Require: + * - StructT -isa-> AncestorT + */ + template + class AncestorStructMemberAccessor : public AbstractStructMemberAccessor { + public: + AncestorStructMemberAccessor(std::unique_ptr ancestor_accessor) + : ancestor_accessor_{std::move(ancestor_accessor)} {} + AncestorStructMemberAccessor(AncestorStructMemberAccessor const & x) = default; + virtual ~AncestorStructMemberAccessor() = default; - static std::unique_ptr - adopt(std::unique_ptr ancestor_accessor) { - return std::unique_ptr - (new AncestorStructMemberAccessor(std::move(ancestor_accessor))); - } /*adopt*/ + static std::unique_ptr + adopt(std::unique_ptr ancestor_accessor) { + return std::unique_ptr + (new AncestorStructMemberAccessor(std::move(ancestor_accessor))); + } /*adopt*/ - void * address_impl(StructT * self_addr) const { - /* to use access-via-ancestor, need to convert to ancestor pointer */ - AncestorT * ancestor_addr = self_addr; + 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*/ + return this->ancestor_accessor_->address(ancestor_addr); + } /*address_impl*/ - // ----- inherited from AbstractStructMemberAccessor ----- + // ----- inherited from AbstractStructMemberAccessor ----- #ifdef OBSOLETE - virtual TaggedPtr member_tp(void * struct_addr) const override { - AncestorT * ancestor_addr = reinterpret_cast(struct_addr); + virtual TaggedPtr member_tp(void * struct_addr) const override { + AncestorT * ancestor_addr = reinterpret_cast(struct_addr); - return this->ancestor_accessor_->member_tp(ancestor_addr); - } /*member_tp*/ + return this->ancestor_accessor_->member_tp(ancestor_addr); + } /*member_tp*/ #endif - virtual TypeDescr struct_td() const override { return EstablishTypeDescr::establish(); } - virtual TypeDescr member_td() const override { return this->ancestor_accessor_->member_td(); } + virtual TypeDescr struct_td() const override { return EstablishTypeDescr::establish(); } + virtual TypeDescr member_td() const override { return this->ancestor_accessor_->member_td(); } - virtual void * address(void * struct_addr) const override { - return this->address_impl(reinterpret_cast(struct_addr)); - } + virtual void * address(void * struct_addr) const override { + return this->address_impl(reinterpret_cast(struct_addr)); + } - virtual std::unique_ptr clone() const override { - return std::unique_ptr - (new AncestorStructMemberAccessor(std::move(this->ancestor_accessor_->clone()))); - } /*clone*/ + virtual std::unique_ptr clone() const override { + return std::unique_ptr + (new AncestorStructMemberAccessor(std::move(this->ancestor_accessor_->clone()))); + } /*clone*/ - private: - /* .ancestor_accessor fetches some particular member of AncestorT */ - std::unique_ptr ancestor_accessor_; - }; /*AncestorStructMemberAccessor*/ + private: + /* .ancestor_accessor fetches some particular member of AncestorT */ + std::unique_ptr ancestor_accessor_; + }; /*AncestorStructMemberAccessor*/ - /* describes a member of a struct/class - * see [reflect/StructReflector.hpp] - */ - class StructMember { - public: - StructMember() = default; - StructMember(std::string const & name, - std::unique_ptr accessor) - : member_name_{name}, accessor_{std::move(accessor)} {} - StructMember(StructMember && x) - : member_name_{std::move(x.member_name_)}, - accessor_{std::move(x.accessor_)} {} + /* describes a member of a struct/class + * see [reflect/StructReflector.hpp] + */ + class StructMember { + public: + StructMember() = default; + StructMember(std::string const & name, + std::unique_ptr accessor) + : member_name_{name}, accessor_{std::move(accessor)} {} + StructMember(StructMember && x) + : member_name_{std::move(x.member_name_)}, + accessor_{std::move(x.accessor_)} {} - static StructMember null(); + static StructMember null(); - std::string const & member_name() const { return member_name_; } + 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); } + TaggedPtr get_member_tp(void * struct_addr) const { return this->accessor_->member_tp(struct_addr); } + TypeDescr get_struct_td() const { return this->accessor_->struct_td(); } + TypeDescr get_member_td() const { return this->accessor_->member_td(); } + //void * get_member_addr(void * struct_addr) const { return this->accessor_->address(struct_addr); } - /* make copy that accesses this member, but starting - * from pointer to some derived class DescendantT, - * instead of from container type StructT known to (but not exposed by) *this - */ - template - StructMember for_descendant() const { - assert(EstablishTypeDescr::establish() == this->get_struct_td()); + /* make copy that accesses this member, but starting + * from pointer to some derived class DescendantT, + * instead of from container type StructT known to (but not exposed by) *this + */ + template + StructMember for_descendant() const { + assert(EstablishTypeDescr::establish() == this->get_struct_td()); - return StructMember(this->member_name(), - std::move(AncestorStructMemberAccessor::adopt - (std::move(this->accessor_->clone())))); - } /*for_descendant*/ + return StructMember(this->member_name(), + std::move(AncestorStructMemberAccessor::adopt + (std::move(this->accessor_->clone())))); + } /*for_descendant*/ - StructMember & operator=(StructMember && x) { - member_name_ = std::move(x.member_name_); - accessor_ = std::move(x.accessor_); - return *this; - } + StructMember & operator=(StructMember && x) { + member_name_ = std::move(x.member_name_); + accessor_ = std::move(x.accessor_); + return *this; + } - private: - /* member name, e.g. foo if - * struct StructT { MemberT foo; } - */ - std::string member_name_; - /* T recd; - * this->accessor_->address_impl(&recd) ==> &(recd.member) - */ - std::unique_ptr accessor_; - }; /*StructMember*/ -} /*namespace reflect*/ + private: + /* member name, e.g. foo if + * struct StructT { MemberT foo; } + */ + std::string member_name_; + /* T recd; + * this->accessor_->address_impl(&recd) ==> &(recd.member) + */ + std::unique_ptr accessor_; + }; /*StructMember*/ + } /*namespace reflect*/ } /*namespace xo*/ /* end StructMember.hpp */ diff --git a/include/xo/reflect/struct/StructTdx.hpp b/include/xo/reflect/struct/StructTdx.hpp index 15eebe9b..0cd524d8 100644 --- a/include/xo/reflect/struct/StructTdx.hpp +++ b/include/xo/reflect/struct/StructTdx.hpp @@ -67,7 +67,9 @@ namespace xo { return TypeDescrExtra::most_derived_self_tp(object_td, object); } } + /* object argument ignored for structs, since size is fixed */ virtual uint32_t n_child(void * /*object*/) const override { return this->member_v_.size(); } + virtual uint32_t n_child_fixed() const override { return this->member_v_.size(); } virtual TaggedPtr child_tp(uint32_t i, void * object) const override; virtual std::string const & struct_member_name(uint32_t i) const override; virtual StructMember const * struct_member(uint32_t i) const override; diff --git a/include/xo/reflect/vector/VectorTdx.hpp b/include/xo/reflect/vector/VectorTdx.hpp index 84e414b4..dcdc20c1 100644 --- a/include/xo/reflect/vector/VectorTdx.hpp +++ b/include/xo/reflect/vector/VectorTdx.hpp @@ -17,10 +17,14 @@ namespace xo { /* named ctor idiom. create new instance for a vector type */ //static std::unique_ptr make(); + /** @brief true if array elements are stored at regularly-spaced offsetts **/ + virtual bool has_contiguous_storage() const = 0; + // ----- Inherited from TypeDescrExtra ----- virtual Metatype metatype() const override { return Metatype::mt_vector; } virtual uint32_t n_child(void * object) const override = 0; + virtual uint32_t n_child_fixed() const override = 0; virtual TaggedPtr child_tp(uint32_t i, void * object) const override = 0; /* (forbidden) */ virtual std::string const & struct_member_name(uint32_t i) const override; @@ -41,12 +45,16 @@ namespace xo { return std::unique_ptr(new StlVectorTdx()); } /*make*/ + virtual bool has_contiguous_storage() const override { return true; } + virtual uint32_t n_child(void * object) const override { target_t * vec = reinterpret_cast(object); return vec->size(); } /*n_child*/ + virtual uint32_t n_child_fixed() const override { return 0; /*unknown*/ } + virtual TaggedPtr child_tp(uint32_t i, void * object) const override { target_t * vec = reinterpret_cast(object); @@ -61,7 +69,10 @@ namespace xo { */ template - using StdArrayTdx = StlVectorTdx>; + class StdArrayTdx : public StlVectorTdx> { + virtual uint32_t n_child(void * /*object*/) const override { return N; } + virtual uint32_t n_child_fixed() const override { return N; } + }; /*StdArrayTdx*/ // ----- std::vector ----- @@ -77,12 +88,18 @@ namespace xo { return std::unique_ptr(new StdVectorTdx()); } /*make*/ + virtual bool has_contiguous_storage() const override { return true; } + virtual uint32_t n_child(void * object) const override { target_t * vec = reinterpret_cast(object); return vec->size(); } /*n_child*/ + virtual uint32_t n_child_fixed() const override { + return 0; /* not known without object */ + } + virtual TaggedPtr child_tp(uint32_t i, void * object) const override { target_t * vec = reinterpret_cast(object); diff --git a/src/reflect/TypeDescrExtra.cpp b/src/reflect/TypeDescrExtra.cpp index e53a216b..9e82fc8e 100644 --- a/src/reflect/TypeDescrExtra.cpp +++ b/src/reflect/TypeDescrExtra.cpp @@ -12,7 +12,7 @@ namespace xo { namespace reflect { TaggedPtr TypeDescrExtra::most_derived_self_tp(TypeDescrBase const * object_td, - void * object) const + void * object) const { return TaggedPtr(object_td, object); } /*most_derived_self_tp*/ @@ -24,7 +24,7 @@ namespace xo { static std::string s_null; return s_null; } /*struct_member_name*/ - + StructMember const * TypeDescrExtra::struct_member(uint32_t /*i*/) const { assert(false); diff --git a/src/reflect/atomic/AtomicTdx.cpp b/src/reflect/atomic/AtomicTdx.cpp index 2e04ab4d..86e5d5ba 100644 --- a/src/reflect/atomic/AtomicTdx.cpp +++ b/src/reflect/atomic/AtomicTdx.cpp @@ -2,6 +2,7 @@ #include "atomic/AtomicTdx.hpp" #include "TaggedPtr.hpp" +#include namespace xo { namespace reflect { diff --git a/src/reflect/struct/StructTdx.cpp b/src/reflect/struct/StructTdx.cpp index 593ad388..bf969724 100644 --- a/src/reflect/struct/StructTdx.cpp +++ b/src/reflect/struct/StructTdx.cpp @@ -3,53 +3,53 @@ #include "struct/StructTdx.hpp" namespace xo { - using std::uint32_t; + using std::uint32_t; - namespace reflect { - std::unique_ptr - StructTdx::make(std::vector member_v, - bool have_to_self_tp, - std::function to_self_tp) - { - return std::unique_ptr(new StructTdx(std::move(member_v), - have_to_self_tp, - std::move(to_self_tp))); - } /*make*/ + namespace reflect { + std::unique_ptr + StructTdx::make(std::vector member_v, + bool have_to_self_tp, + std::function to_self_tp) + { + return std::unique_ptr(new StructTdx(std::move(member_v), + have_to_self_tp, + std::move(to_self_tp))); + } /*make*/ - TaggedPtr - StructTdx::child_tp(uint32_t i, void * object) const - { - if (i >= this->member_v_.size()) { - /* TODO: raise exception here? */ - return TaggedPtr::universal_null(); - } + 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]; + StructMember const & member_info = this->member_v_[i]; - return member_info.get_member_tp(object); + return member_info.get_member_tp(object); - } /*get_child*/ + } /*get_child*/ - std::string const & - StructTdx::struct_member_name(uint32_t i) const - { - StructMember const * sm = this->struct_member(i); + 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*/ + 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; - } + 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*/ + return &(this->member_v_[i]); + } /*struct_member*/ + } /*namespace reflect*/ } /*namespace xo*/ /* end StructTdx.cpp */ From 2b9aff3640c69d01f06d7b4f7447ba35a4b6dc9b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 19:37:16 -0400 Subject: [PATCH 1189/2524] xo-reflect: + TypeDescr:: n_child_fixed(), fixed_child_td(i) --- include/xo/reflect/TypeDescr.hpp | 13 ++++++++++++- include/xo/reflect/TypeDescrExtra.hpp | 6 ++++++ include/xo/reflect/atomic/AtomicTdx.hpp | 1 + include/xo/reflect/function/FunctionTdx.hpp | 1 + include/xo/reflect/struct/StructTdx.hpp | 1 + include/xo/reflect/vector/VectorTdx.hpp | 10 ++++++++++ src/reflect/atomic/AtomicTdx.cpp | 6 ++++++ src/reflect/function/FunctionTdx.cpp | 6 ++++++ src/reflect/struct/StructTdx.cpp | 14 +++++++++++++- 9 files changed, 56 insertions(+), 2 deletions(-) diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index 870d3561..a91befc8 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -305,7 +305,18 @@ namespace xo { * .n_child() reports #of instance variables (that have been reflected) */ uint32_t n_child(void * object) const { return this->tdextra_->n_child(object); } - /** in some circumstances can use this with object=nullptr **/ + /** number of children, if that number is fixed at compile time. otherwise 0 + **/ + uint32_t n_child_fixed() const { return this->tdextra_->n_child_fixed(); } + /** TypeDescr for i'th child, using only information available at compile time. + * e.g. for vectors/pointers, always returns ElementType. + **/ + TypeDescr fixed_child_td(uint32_t i) const { return this->tdextra_->fixed_child_td(i); } + /** TaggedPtr to child @p i. + * Will report most-derived-type for type tag, + * so may refer to a proper subtype (e.g. derived class) of the type + * reported by @c fixed_child_td(i) + **/ TaggedPtr child_tp(uint32_t i, void * object) const; /* require: diff --git a/include/xo/reflect/TypeDescrExtra.hpp b/include/xo/reflect/TypeDescrExtra.hpp index 68291654..f53508ae 100644 --- a/include/xo/reflect/TypeDescrExtra.hpp +++ b/include/xo/reflect/TypeDescrExtra.hpp @@ -59,6 +59,12 @@ namespace xo { * Will also return 0 for types like {bool, int, long} (because number is zero) **/ virtual uint32_t n_child_fixed() const = 0; + /** type description for i'th child, based on information available at compile time. + * For vectors/pointers, this always refers to element type. + * + * nullptr for atomics + **/ + virtual const TypeDescrBase * fixed_child_td(uint32_t i) const = 0; virtual TaggedPtr child_tp(uint32_t i, void * object) const = 0; /* require: * .is_struct() diff --git a/include/xo/reflect/atomic/AtomicTdx.hpp b/include/xo/reflect/atomic/AtomicTdx.hpp index 9bd5e567..d5068199 100644 --- a/include/xo/reflect/atomic/AtomicTdx.hpp +++ b/include/xo/reflect/atomic/AtomicTdx.hpp @@ -26,6 +26,7 @@ namespace xo { virtual uint32_t n_child(void * /*object*/) const override { return 0; } virtual uint32_t n_child_fixed() const override { return 0; } virtual TaggedPtr child_tp(uint32_t /*i*/, void * /*object*/) const override; + virtual const TypeDescrBase * fixed_child_td(uint32_t /*i*/) const override; virtual std::string const & struct_member_name(uint32_t i) const override; //virtual StructMember const * struct_member(uint32_t /*i*/) const override { return nullptr; } diff --git a/include/xo/reflect/function/FunctionTdx.hpp b/include/xo/reflect/function/FunctionTdx.hpp index 64b02da3..02f60558 100644 --- a/include/xo/reflect/function/FunctionTdx.hpp +++ b/include/xo/reflect/function/FunctionTdx.hpp @@ -35,6 +35,7 @@ namespace xo { virtual uint32_t n_child(void * /*object*/) const override { return 0; } virtual uint32_t n_child_fixed() const override { return 0; } virtual TaggedPtr child_tp(uint32_t i, void * object) const override; + virtual TypeDescr fixed_child_td(uint32_t i) const override; const std::string & struct_member_name(uint32_t i) const override; virtual const FunctionTdxInfo * fn_info() const override { return &info_; } diff --git a/include/xo/reflect/struct/StructTdx.hpp b/include/xo/reflect/struct/StructTdx.hpp index 0cd524d8..57afe277 100644 --- a/include/xo/reflect/struct/StructTdx.hpp +++ b/include/xo/reflect/struct/StructTdx.hpp @@ -71,6 +71,7 @@ namespace xo { virtual uint32_t n_child(void * /*object*/) const override { return this->member_v_.size(); } virtual uint32_t n_child_fixed() const override { return this->member_v_.size(); } virtual TaggedPtr child_tp(uint32_t i, void * object) const override; + virtual TypeDescr fixed_child_td(uint32_t i) const override; virtual std::string const & struct_member_name(uint32_t i) const override; virtual StructMember const * struct_member(uint32_t i) const override; diff --git a/include/xo/reflect/vector/VectorTdx.hpp b/include/xo/reflect/vector/VectorTdx.hpp index dcdc20c1..ebd318c2 100644 --- a/include/xo/reflect/vector/VectorTdx.hpp +++ b/include/xo/reflect/vector/VectorTdx.hpp @@ -26,6 +26,7 @@ namespace xo { virtual uint32_t n_child(void * object) const override = 0; virtual uint32_t n_child_fixed() const override = 0; virtual TaggedPtr child_tp(uint32_t i, void * object) const override = 0; + virtual TypeDescr fixed_child_td(uint32_t i) const override = 0; /* (forbidden) */ virtual std::string const & struct_member_name(uint32_t i) const override; }; /*VectorTdx*/ @@ -33,6 +34,7 @@ namespace xo { // ----- StlVectorTdx ----- /* require: + * - VectorT::value_type * - VectorT.size() * - VectorT[int] :: lvalue */ @@ -60,6 +62,10 @@ namespace xo { return establish_most_derived_tp(&((*vec)[i])); } /*child_tp*/ + + virtual TypeDescr fixed_child_td(uint32_t /*i*/) const override { + return EstablishTypeDescr::establish(); + } }; /*StlVectorTdx*/ // ----- std::array ----- @@ -105,6 +111,10 @@ namespace xo { return establish_most_derived_tp(&((*vec)[i])); } + + virtual TypeDescr fixed_child_td(uint32_t /*i*/) const override { + return EstablishTypeDescr::establish(); + } }; /*StdVectorTdx*/ } /*namespace reflect*/ diff --git a/src/reflect/atomic/AtomicTdx.cpp b/src/reflect/atomic/AtomicTdx.cpp index 86e5d5ba..bb7e1b8d 100644 --- a/src/reflect/atomic/AtomicTdx.cpp +++ b/src/reflect/atomic/AtomicTdx.cpp @@ -2,6 +2,7 @@ #include "atomic/AtomicTdx.hpp" #include "TaggedPtr.hpp" +#include "TypeDescr.hpp" #include namespace xo { @@ -16,6 +17,11 @@ namespace xo { return TaggedPtr::universal_null(); } /*child_tp*/ + TypeDescr + AtomicTdx::fixed_child_td(uint32_t /*i*/) const { + return nullptr; + } + std::string const & AtomicTdx::struct_member_name(uint32_t i) const { return TypeDescrExtra::struct_member_name(i); diff --git a/src/reflect/function/FunctionTdx.cpp b/src/reflect/function/FunctionTdx.cpp index 27c0f8fc..551cfef0 100644 --- a/src/reflect/function/FunctionTdx.cpp +++ b/src/reflect/function/FunctionTdx.cpp @@ -2,6 +2,7 @@ #include "function/FunctionTdx.hpp" #include "TaggedPtr.hpp" +#include "TypeDescr.hpp" namespace xo { namespace reflect { @@ -36,6 +37,11 @@ namespace xo { return TaggedPtr::universal_null(); } + TypeDescr + FunctionTdx::fixed_child_td(uint32_t /*i*/) const { + return nullptr; + } + const std::string & FunctionTdx::struct_member_name(uint32_t i) const { diff --git a/src/reflect/struct/StructTdx.cpp b/src/reflect/struct/StructTdx.cpp index bf969724..c9edc11b 100644 --- a/src/reflect/struct/StructTdx.cpp +++ b/src/reflect/struct/StructTdx.cpp @@ -1,6 +1,7 @@ /* @file StructTdx.cpp */ #include "struct/StructTdx.hpp" +#include "TypeDescr.hpp" namespace xo { using std::uint32_t; @@ -24,12 +25,23 @@ namespace xo { return TaggedPtr::universal_null(); } - StructMember const & member_info = this->member_v_[i]; + const StructMember & member_info = this->member_v_[i]; return member_info.get_member_tp(object); } /*get_child*/ + TypeDescr + StructTdx::fixed_child_td(uint32_t i ) const + { + if (i >= this->member_v_.size()) + return nullptr; + + const StructMember & member_info = this->member_v_[i]; + + return member_info.get_member_td(); + } /*fixed_child_td*/ + std::string const & StructTdx::struct_member_name(uint32_t i) const { From 771ccdd30839a30c5bac547f9af21e079978dfb6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 00:39:58 -0400 Subject: [PATCH 1190/2524] xo-reflect: cosmetic: indenting --- include/xo/reflect/TaggedRcptr.hpp | 116 ++++++++++++++--------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/include/xo/reflect/TaggedRcptr.hpp b/include/xo/reflect/TaggedRcptr.hpp index 3e06af64..5d051070 100644 --- a/include/xo/reflect/TaggedRcptr.hpp +++ b/include/xo/reflect/TaggedRcptr.hpp @@ -11,78 +11,78 @@ #include "xo/refcnt/Refcounted.hpp" namespace xo { - namespace reflect { - /* Tagged reference-counted pointer. - * Like TaggedPtr, but also maintains reference count. - * - * note that refcounting behavior is lost if assigned to a TaggedPtr variable! - */ - class TaggedRcptr : public TaggedPtr { - public: - using Refcount = ref::Refcount; + 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()); - } + 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] */ + /* causes #include cycle, see [reflect/Reflect.hpp] */ #ifdef NOT_IN_USE - /* require: T --isa--> ref::Refcount */ - template - static TaggedRcptr make(T * x) { return TaggedRcptr(Reflect::require(), x); } + /* require: T --isa--> ref::Refcount */ + template + static TaggedRcptr make(T * x) { return TaggedRcptr(Reflect::require(), x); } #endif - Refcount * rc_address() const { - return reinterpret_cast(this->address()); - } /*rc_address*/ + Refcount * rc_address() const { + return reinterpret_cast(this->address()); + } /*rc_address*/ - TaggedRcptr & operator=(TaggedRcptr const & rhs) { - Refcount * x = rhs.rc_address(); - Refcount * old = this->rc_address(); + TaggedRcptr & operator=(TaggedRcptr const & rhs) { + Refcount * x = rhs.rc_address(); + Refcount * old = this->rc_address(); - TaggedPtr::operator=(rhs); + TaggedPtr::operator=(rhs); - if (x != old) { - intrusive_ptr_release(old); - intrusive_ptr_add_ref(x); - } + if (x != old) { + intrusive_ptr_release(old); + intrusive_ptr_add_ref(x); + } - return *this; - } /*operator=*/ + 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_); + 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=*/ + return *this; + } /*operator=*/ - void display(std::ostream & os) const; - std::string display_string() const; - }; /*TaggedRcptr*/ + 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<<*/ + inline std::ostream & operator<<(std::ostream & os, TaggedRcptr const & x) { + x.display(os); + return os; + } /*operator<<*/ - } /*namespace reflect*/ + } /*namespace reflect*/ } /*namespace xo*/ /* end TaggedRcptr.hpp */ From 2c51a1e1a0f8ff19b5a7fef72e171bd6ff11613b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 00:40:11 -0400 Subject: [PATCH 1191/2524] xo-reflect: promote std::string to always-reflect --- src/reflect/TypeDescr.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp index 9bb4bc39..4d9e3d50 100644 --- a/src/reflect/TypeDescr.cpp +++ b/src/reflect/TypeDescr.cpp @@ -314,6 +314,7 @@ namespace xo { Reflect::require(); Reflect::require(); Reflect::require(); + Reflect::require(); } /*ctor*/ TypeDescrTable From 58e143f1b1c18b902dc1741f1bbfc99d054bf295 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 00:40:28 -0400 Subject: [PATCH 1192/2524] xo-reflect: + Object (cache TypeId for dispatching) --- include/xo/reflect/Object.hpp | 44 +++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 include/xo/reflect/Object.hpp diff --git a/include/xo/reflect/Object.hpp b/include/xo/reflect/Object.hpp new file mode 100644 index 00000000..8fb8c156 --- /dev/null +++ b/include/xo/reflect/Object.hpp @@ -0,0 +1,44 @@ +** @file Object.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/reflect/SelfTagging.hpp" +//#include + +namespace xo { + namespace reflect { + /** @class Object + * + * @brief A swiss-army-knife base class for runtime polymorphism. + * + * Promote using this: + * - for interpreter integration (see xo-expression / xo-jit) + * - to allow reasonably efficient type dispatching - + * don't need to pay for a function call to find out dispatching type. + **/ + class Object : public reflect::SelfTagging { + public: + using TypeId = xo::reflect::TypeId; + + Object(TypeId type_id) : type_id_{type_id} {} + + private: + /** unique id number for this object's type + * + * Caches the value of this->self_tp().td()->id() + * + * Notes: + * 1. may want to record metatype also + * 2. a few builtin types have well-known type_ids. + * see TypeDescrTable ctor in xo-reflect. + **/ + TypeId type_id_; + }; + } /*namespace obj*/ +} /*namespace xo*/ + + +/** end Object.hpp **/ From 908c3ae50b8a8c5f035e78772ba06ab62b5e39b0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 00:45:18 -0400 Subject: [PATCH 1193/2524] xo-reflect: bugfix: missing char in Object.hpp --- include/xo/reflect/Object.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/reflect/Object.hpp b/include/xo/reflect/Object.hpp index 8fb8c156..fc7f1607 100644 --- a/include/xo/reflect/Object.hpp +++ b/include/xo/reflect/Object.hpp @@ -1,4 +1,4 @@ -** @file Object.hpp +/** @file Object.hpp * * Author: Roland Conybeare **/ From 85837924a535c4864c57c1a3536af3d086fc14d2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 01:33:57 -0400 Subject: [PATCH 1194/2524] xo-cmake: + default flags when CMAKE_BUILD_TYPE empty --- cmake/xo_macros/xo_cxx.cmake | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 4533f929..169941c7 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -68,6 +68,36 @@ macro(xo_toplevel_testing_options) PROPERTY targets "") endmacro() +# default build (cmake -DCMAKE_BUILD_TYPE= path/to/source) +# +macro(xo_toplevel_default_config2) + if ("${CMAKE_BUILD_TYPE}" STREQUAL "") + # clear out hardwired default. + # we want to override project-level defaults, + # but need to prevent interference from hardwired defaults + # (the problem with non-empty hardwired defaults is that we can't tell if they've + # been set on the command line) + # + set(CMAKE_CXX_FLAGS_DEFAULT "") + + # CMAKE_CXX_FLAGS_DEBUG is built-in to cmake and has non-empty default. + # -> we cannot tell whether it was set on the command line + # -> use PROJECT_CXX_FLAGS_DEBUG instead + # + # built-in default value is -g; can hardwire different project policy here + # + if (NOT DEFINED PROJECT_CXX_FLAGS_DEFAULT) + set(PROJECT_CXX_FLAGS_DEFAULT ${PROJECT_CXX_FLAGS} -fno-strict-aliasing -O + CACHE STRING "default c++ compiler flags") + endif() + + message(STATUS "PROJECT_CXX_FLAGS_DEFAULT: default c++ flags are [${PROJECT_CXX_FLAGS_DEFAULT}]") + + # note no $ selector here + add_compile_options("${PROJECT_CXX_FLAGS_DEFAULT}") + endif() +endmacro() + # release build (cmake -DCMAKE_BUILD_TYPE=release path/to/source) # macro(xo_toplevel_release_config2) @@ -87,7 +117,7 @@ macro(xo_toplevel_release_config2) # built-in default value is -march=native -O3 -DNDEBUG # if (NOT DEFINED PROJECT_CXX_FLAGS_RELEASE) - set(PROJECT_CXX_FLAGS_RELEASE ${PROJECT_CXX_FLAGS} -march=native -O3 -DNDEBUG + set(PROJECT_CXX_FLAGS_RELEASE ${PROJECT_CXX_FLAGS} -fno-strict-aliasing -march=native -O3 -DNDEBUG CACHE STRING "release c++ compiler flags") endif() @@ -116,7 +146,7 @@ macro(xo_toplevel_debug_config2) # built-in default value is -g; can hardwire different project policy here # if (NOT DEFINED PROJECT_CXX_FLAGS_DEBUG) - set(PROJECT_CXX_FLAGS_DEBUG ${PROJECT_CXX_FLAGS} -ggdb -Og + set(PROJECT_CXX_FLAGS_DEBUG ${PROJECT_CXX_FLAGS} -fno-strict-aliasing -ggdb -Og CACHE STRING "debug c++ compiler flags") endif() @@ -137,7 +167,7 @@ macro(xo_toplevel_asan_config2) set(CMAKE_CXX_FLAGS_ASAN "") if (NOT DEFINED PROJECT_CXX_FLAGS_ASAN) - set(PROJECT_CXX_FLAGS_ASAN ${PROJECT_CXX_FLAGS} -Og -fsanitize=address + set(PROJECT_CXX_FLAGS_ASAN ${PROJECT_CXX_FLAGS} -fno-strict-aliasing -Og -fsanitize=address CACHE STRING "asan c++ compiler flags") endif() From 5fb7745c66ba6deb4230ba41f079f5401df9de90 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 01:34:24 -0400 Subject: [PATCH 1195/2524] xo-cmake: + FAQ --- FAQ | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 FAQ diff --git a/FAQ b/FAQ new file mode 100644 index 00000000..e30a679f --- /dev/null +++ b/FAQ @@ -0,0 +1,120 @@ +# Unit test build can't find library: + +Missing dependency at link time + +``` +set(SELF_EXE mumble) +set(SELF_SRCS mumble.cpp) + +xo_add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_self_dependency(${SELF_EXE} xo_foo) +``` + +and build fails with `cannot find -lxo_bar`. + +Possible causes: + +1. missing cmake export for a dependency of `xo_foo`. + Check `xo_foo/cmake/xo_fooConfig.cmake.in`: + + If `xo_foo` depends on `xo_bar`, then cmake export + needs to have `find_dependency(xo_bar)` + +``` +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(xo_bar) +find_dependency(xo_maybemoar) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") +``` + +# Howto introduce sphinx documentation to an XO project + +REMINDER: must re-run `cmake` after introducing skeleton, since build copies files from docs/ to .build directory + +Minimal skeleton for docs/ directory: + +``` +xo-foo ++- docs + +- CMakeLists.txt + +- conf.py + +- _static + +- index.rst +``` + +CMakeLists.txt: +``` +# xo-foo/docs/CMakeLists.txt + +xo_doxygen_collect_deps() +xo_docdir_doxygen_config() +xo_docdir_sphinx_config( + index.rst +) +``` + +conf.py: +``` +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'xo jit documentation' +copyright = '2024, Roland Conybeare' +author = 'Roland Conybeare' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +#extensions = [] +extensions = [ "breathe", + "sphinx.ext.mathjax", # inline math + "sphinx.ext.autodoc", # generate info from docstrings + "sphinxcontrib.ditaa", # diagrams-through-ascii-art + "sphinxcontrib.plantuml" # text -> uml diagrams + ] + +# note: breathe requires doxygen xml output -> must have GENERATE_XML = YES in Doxyfile.in +# match project name in Doxyfile.in +breathe_default_project = "xodoxxml" + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +pygments_style = 'sphinx' + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +#html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] +html_favicon = '_static/img/favicon.ico' +``` + +index.rst: +``` +xo-foo documentation +==================== + +text here + +.. toctree:: + :maxdepth: 2 + :caption: xo-foo contents + + genindex + search +``` + +_static: + +copy from xo-unit/docs/_static. Just need .ico From aff3e6461963d35a039b31f5caaaeae2ef122c76 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 01:36:22 -0400 Subject: [PATCH 1196/2524] xo-cmake: bugfix: missed invocation of xo_toplevel_default_config() --- cmake/xo_macros/xo_cxx.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 169941c7..06e5e8c3 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -186,12 +186,14 @@ macro(xo_toplevel_asan_config2) endmacro() # support for +# cmake -DCMAKE_BUILD_TYPE= # cmake -DCMAKE_BUILD_TYPE=release # cmake -DCMAKE_BUILD_TYPE=debug -# cmake -DCMAKE_BUILD_TYPE+asan +# cmake -DCMAKE_BUILD_TYPE=asan # cmake -DCMAKE_BUILD_TYPE=coverage # macro(xo_toplevel_config2) + xo_toplevel_default_config2() xo_toplevel_release_config2() xo_toplevel_debug_config2() xo_toplevel_asan_config2() From f971f18ecde5cb81fa511164d9a8939e8c43b6d0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 13:10:00 -0400 Subject: [PATCH 1197/2524] xo-jit: inspect struct alignment in utest --- utest/MachPipeline.test.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp index 7a309f74..5e0fafc4 100644 --- a/utest/MachPipeline.test.cpp +++ b/utest/MachPipeline.test.cpp @@ -231,8 +231,12 @@ namespace xo { REQUIRE(struct_td); + // ----- build AST ----- + auto fn_ast = make_ratio(); + // ----- convert AST -> llvm IR datastructure ----- + llvm::Value * llvm_ircode = jit->codegen_toplevel(fn_ast); /* TODO: printer for llvm::Value* */ @@ -249,12 +253,38 @@ namespace xo { REQUIRE(llvm_ircode); + // ----- inspect alignment ----- + + llvm::StructType * struct_llvm_type + = static_cast(jit->codegen_type(struct_td)); + + auto struct_layout = jit->data_layout().getStructLayout(struct_llvm_type); + + log && log(xtag("struct-size", struct_layout->getSizeInBytes()), + xtag("struct-alignment", struct_layout->getAlignment().value())); + for (int i = 0, n = struct_llvm_type->getNumElements(); i < n; ++i) { + llvm::TypeSize llvm_tz = struct_layout->getElementOffset(i); + auto offset = reinterpret_cast(struct_td->struct_member(i).get_member_tp(nullptr).address()); + + log && log(xtag("i", i), + xtag("name(c++)", struct_td->struct_member(i).member_name()), + xtag("type(c++)", struct_td->struct_member(i).get_member_td()->short_name()), + xtag("offset(c++)", offset), + xtag("offset(llvm)", llvm_tz.getKnownMinValue())); + + REQUIRE(offset == llvm_tz.getKnownMinValue()); + } + + // ----- generate JIT machine code ----- + jit->machgen_current_module(); log && log("execution session after codegen:"); //log && log(jit->xsession()); // segfaults jit->dump_execution_session(); + // ----- verify: lookup symbol + /** lookup compiled function pointer in jit **/ auto llvm_addr = jit->lookup_symbol(fn_ast->name()); @@ -276,6 +306,8 @@ namespace xo { REQUIRE(fn_ptr); + // ---- invoke compiled function ----- + auto value = (*fn_ptr)(2, 3); log && log(xtag("value.num", value.num()), From 465be8ddd2044ebf3b082ec93e4dea54b0b3e805 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 13:10:36 -0400 Subject: [PATCH 1198/2524] xo-jit: drop isPacked=true when creating struct types --- src/jit/MachPipeline.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 5228cc09..e2a69e7c 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -247,9 +247,6 @@ namespace xo { std::string struct_name = std::string(struct_td->short_name()); /* structs with names: within an llvmcontext, must be unique - * - * If we don't set isPacked, then padding will be chosen based on DataLayout, - * which might C++ compiler's padding, but no guarantees. * * We can however compare the offsets recorded in xo::reflect with * offsets chosen by llvm, *once we've created the llvm type* @@ -268,7 +265,7 @@ namespace xo { = llvm::StructType::create(llvm_cx_ref, llvm_membertype_v, llvm::StringRef(struct_name), - true /*isPacked*/); + false /*!isPacked*/); /* TODO: inspect (how) offsets that llvm is using * we need them to match what C++ chose From 27d8f05b528c7ff1992d5c71d81e079a61848b96 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 13:11:02 -0400 Subject: [PATCH 1199/2524] xo-jit: handle pointer types --- src/jit/MachPipeline.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index e2a69e7c..69cf0d02 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -280,6 +280,22 @@ namespace xo { return llvm_struct_type; } /*struct_td_to_llvm_type*/ + llvm::PointerType * + pointer_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr pointer_td) + { + assert(pointer_td->is_pointer()); + + TypeDescr dest_td = pointer_td->fixed_child_td(0); + + llvm::Type * llvm_dest_type = td_to_llvm_type(llvm_cx, dest_td); + + llvm::PointerType * llvm_ptr_type + = llvm::PointerType::getUnqual(llvm_dest_type); + + return llvm_ptr_type; + } /*pointer_td_llvm_type*/ + llvm::Type * td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); @@ -291,6 +307,8 @@ namespace xo { return function_td_to_llvm_fnptr_type(llvm_cx, td); } else if (td->is_struct()) { return struct_td_to_llvm_type(llvm_cx, td); + } else if (td->is_pointer()) { + return pointer_td_to_llvm_type(llvm_cx, td); } else if (Reflect::is_native(td)) { return llvm::Type::getInt1Ty(llvm_cx_ref); } else if (Reflect::is_native(td)) { From be3e62f75a1f73f2a0ef9750bd0a2446769bf405 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 13:11:22 -0400 Subject: [PATCH 1200/2524] xo-jit: helper functions -> explicit stack frames [wip, not used] --- src/jit/MachPipeline.cpp | 173 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 172 insertions(+), 1 deletion(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 69cf0d02..386f16e4 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -700,6 +700,176 @@ namespace xo { return fn; } /*codegen_lambda_decl*/ + namespace { + /** A function type: + * + * _baseframe* (*) (_baseframe* + **/ + llvm::FunctionType * + require_baseframe_unwind_llvm_type(xo::ref::brw llvm_cx, + llvm::PointerType * frameptr_llvm_type) + { + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(2); + + /* 1st arg is frame pointer */ + llvm_argtype_v.push_back(frameptr_llvm_type); + + /* 2nd arg is an i32. + * 0 -> unwind. + * 1 -> lift to heap (someday) + */ + llvm_argtype_v.push_back + (llvm::Type::getInt32Ty(llvm_cx->llvm_cx_ref())); + + /* return value is frame pointer */ + llvm::Type * retval_llvm_type = frameptr_llvm_type; + + auto * unwind_llvm_type = llvm::FunctionType::get(retval_llvm_type, + llvm_argtype_v, + false /*!varargs*/); + + return unwind_llvm_type; + } /*require_baseframe_unwind_llvm_type*/ + + /** Each lambda gets its own stack frame definition. + * However all the various frame representations share the same 'baseframe' + * prefix. + * + * _baseframe: + * ^ + * | + * +-------+ | + * next_frame [0] | o-------/ + * +-------| + * unwind_fn [1] | o-------> frame* (*)(frame*, ctl) + * +-------+ + * + * This helper function generates an llvm::Type* for a baseframe. + * It only needs to be invoked once (per LlvmContext, I guess ..) + **/ + llvm::StructType * + require_baseframe_llvm_type(xo::ref::brw llvm_cx) + { + /* _baseframe: base type for a stack frame */ + llvm::StructType * frame_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref(), + "_baseframe"); + + /* _baseframe*: pointer to a stack frame */ + llvm::PointerType * frameptr_llvm_type + = llvm::PointerType::getUnqual(frame_llvm_type); + + /* unwind function = frame[1] */ + llvm::FunctionType * unwind_llvm_type + = require_baseframe_unwind_llvm_type(llvm_cx, + frameptr_llvm_type); + + /* _baseframe members */ + std::vector llvm_membertype_v; + { + llvm_membertype_v.reserve(2); + + /* frame[0] = pointer to next frame */ + llvm_membertype_v.push_back(frameptr_llvm_type); + /* frame[1] = unwind function */ + llvm_membertype_v.push_back(unwind_llvm_type); + } + + frame_llvm_type->setBody(frameptr_llvm_type /*frame[0]*/, + unwind_llvm_type /*frame[1]*/); + + return frame_llvm_type; + } /*require_baseframe_llvm_type*/ + + /** need a supporting type for stack frame + * - so we can handle variables with non-trivial dtors + * (e.g. smart pointers) + * - so we can implement nested lexical scoping + * - so we can walk stack for exception handling + * - eventually: so we can eventually implement trampoline + * + * frame representation: + * + * ^ + * | + * +-------+ | + * next_frame [0] | o-------/ + * +-------| + * unwind_fn [1] | o-------> baseframe* (*)(baseframe*, ctl) + * +-------| + * arg[i] [2+i] | ... | + * +-------+ + * + * invoke frame.unwind_fn to dispose of a frame + * - ctl=0 dtor. deal with smart pointers etc. + * - ctl=1 copy. lift frame into heap for lambda capture + * + * every frame is a subtype of _baseframe (ofc llvm doesn't know this). + * See baseframe_llvm_type(), baseframe_unwind_llvm_type() above + * + * editor bait: activation_record_to_llvm_type + **/ + llvm::StructType * + frame_to_llvm_type(xo::ref::brw llvm_cx, + ref::brw lambda, + llvm::Function * lambda_llvm_fn) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + /* frame type doesn't need a name */ + llvm::StructType * frame_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref()); + + /* _baseframe */ + llvm::StructType * baseframe_llvm_type + = require_baseframe_llvm_type(llvm_cx); + + /* _baseframe*: pointer to a generic stack frame */ + llvm::PointerType * baseframeptr_llvm_type + = llvm::PointerType::getUnqual(baseframe_llvm_type); + + /* _baseframe* (*)(_baseframe*, i32) */ + llvm::FunctionType * unwind_llvm_type + = require_baseframe_unwind_llvm_type(llvm_cx, + baseframeptr_llvm_type); + + /* llvm_argtype_v: + * - llvm_argtype_v[0] = llvm::Type* for pointer to next_frame + * - llvm_argtype_v[1] = llvm::Type* for unwind_fn + * - llvm_argtype_v[2+i] = llvm::Type* for lambda->fn_arg(i) + */ + std::vector llvm_argtype_v; + { + llvm_argtype_v.reserve(2 + lambda_llvm_fn->arg_size()); + + /* frame pointer */ + llvm_argtype_v.push_back(baseframeptr_llvm_type); + /* unwind function */ + llvm_argtype_v.push_back(unwind_llvm_type); + + int i_arg = 0; + for (auto & arg : lambda_llvm_fn->args()) { + log && log(xtag("i_arg", i_arg), + xtag("param", std::string(arg.getName()))); + + llvm_argtype_v.push_back(td_to_llvm_type(llvm_cx, + lambda->fn_arg(i_arg))); + + ++i_arg; + } + } + + frame_llvm_type->setBody(llvm_argtype_v); + + return frame_llvm_type; + } /*frame_to_llvm_type*/ + } /*namespace*/ + + llvm::Function * MachPipeline::codegen_lambda_defn(ref::brw lambda, llvm::IRBuilder<> & ir_builder) @@ -724,7 +894,6 @@ namespace xo { return nullptr; } - /* generate function body */ auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); @@ -733,6 +902,8 @@ namespace xo { /** Actual parameters will need their own activation record. * Track its shape here. + * + * Local variables will be formal parameters to a nested lambda; **/ this->env_stack_.push(activation_record()); From 71df4f824da453c53cfcc649de423877d8432301 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 14:38:48 -0400 Subject: [PATCH 1201/2524] xo-jit: progress towards explicit stack frames [wip, incomplete] --- include/xo/jit/MachPipeline.hpp | 7 +++ include/xo/jit/activation_record.hpp | 27 ++++++++- src/jit/MachPipeline.cpp | 82 +++++++++++++++++++++++++++- src/jit/activation_record.cpp | 10 ++-- 4 files changed, 118 insertions(+), 8 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 3e475e92..14003c44 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -152,6 +152,13 @@ namespace xo { std::vector> find_lambdas(ref::brw expr) const; public: + /** codegen helper for a user-defined function. + * create stack slot on behalf of formal parameters. + * linked to (dynamic) callers for stack unwinding + **/ + llvm::AllocaInst * create_entry_frame_alloca(llvm::Function * llvm_fn, + llvm::StructType * frame_llvm_type); + /** codegen helper for a user-defined function (codegen_lambda()): * create stack slot on behalf of some formal parameter to a function, * so we can avoid SSA restriction on function body diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index c2aba2dd..7c70ba3f 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -22,16 +22,41 @@ namespace xo { **/ class activation_record { public: - activation_record() = default; + activation_record(llvm::Function * llvm_fn, + llvm::AllocaInst * frame) : frame_{frame} { + int i_arg = 0; + for (auto & arg : llvm_fn->args()) { + std::string arg_name = std::string(arg.getName()); + name2ix_map_[arg_name] = 2 + i_arg; + } + } + + std::int32_t lookup_var(const std::string & var_name) const; + +#ifdef OBSOLETE llvm::AllocaInst * lookup_var(const std::string & var_name) const; llvm::AllocaInst * alloc_var(const std::string & var_name, llvm::AllocaInst * alloca); +#endif private: + /** stack frame for a user-defined function (lambda) **/ + llvm::AllocaInst * frame_ = nullptr; + + /** for each formal parameter, + * reports its position in stack frame. + * This is the position to use with getelementptr, + * i.e. +2 to skip first two slots, that are reserved + * for nextframe pointer (slot 0) + unwind pointer (slot 1) + **/ + std::map name2ix_map_; + +#ifdef OBSOLETE /** maps named slots in a stack frame to logical addresses **/ std::map frame_; /* <-> kaleidoscope NamedValues */ +#endif }; /*activation_record*/ } /*namespace jit*/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 386f16e4..76fa68ae 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -627,6 +627,42 @@ namespace xo { } /*create_entry_block_alloca*/ + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + llvm::AllocaInst * + MachPipeline::create_entry_frame_alloca(llvm::Function * llvm_fn, + llvm::StructType * frame_llvm_type) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn)); + + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), + llvm_fn->getEntryBlock().begin()); + + if (!frame_llvm_type) + return nullptr; + + if (log) { + std::string llvm_frame_type_str; + llvm::raw_string_ostream ss(llvm_frame_type_str); + frame_llvm_type->print(ss); + + log(xtag("frame_llvm_type", llvm_frame_type_str)); + } + + llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(frame_llvm_type, + nullptr, + llvm_fn->getName()); + + log && log(xtag("alloca", (void*)retval), + xtag("align", retval->getAlign().value()), + xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); + + return retval; + } /*create_entry_frame_alloca*/ + std::vector> MachPipeline::find_lambdas(ref::brw expr) const { @@ -869,7 +905,6 @@ namespace xo { } /*frame_to_llvm_type*/ } /*namespace*/ - llvm::Function * MachPipeline::codegen_lambda_defn(ref::brw lambda, llvm::IRBuilder<> & ir_builder) @@ -900,12 +935,24 @@ namespace xo { ir_builder.SetInsertPoint(block); + /* create stack frame */ + llvm::StructType * frame_llvm_type + = frame_to_llvm_type(llvm_cx_, + lambda, + llvm_fn); + + llvm::AllocaInst * frame_alloca = create_entry_frame_alloca(llvm_fn, + frame_llvm_type); + + if (!frame_alloca) + return nullptr; + /** Actual parameters will need their own activation record. * Track its shape here. * * Local variables will be formal parameters to a nested lambda; **/ - this->env_stack_.push(activation_record()); + this->env_stack_.push(activation_record(llvm_fn, frame_alloca)); { log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); @@ -918,6 +965,7 @@ namespace xo { std::string arg_name = std::string(arg.getName()); +#ifdef OBSOLETE /* stack location for arg[i] */ llvm::AllocaInst * alloca = create_entry_block_alloca(llvm_fn, @@ -928,17 +976,45 @@ namespace xo { this->env_stack_.pop(); return nullptr; } +#endif + + /* need to store args to stack frame */ + + std::int32_t i_slot = env_stack_.top().lookup_var(arg_name); + + if (i_slot == -1) { + /* variable not found! */ + return nullptr; + } + + llvm::Value * i32_zero = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32, 0)); + llvm::Value * i32_slot = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32, i_slot)); + + std::array index_v = { + {i32_zero /*deref frame pointer*/, + i32_slot /*field# relative to frame pointer*/}}; + + /* location in stack frame of arg #i */ + auto arg_ptr + = ir_builder.CreateInBoundsGEP(frame_llvm_type, + frame_alloca, + index_v); /* store on function entry * see codegen_variable() for corresponding load */ - ir_builder.CreateStore(&arg, alloca); + ir_builder.CreateStore(&arg, + alloca /*destination*/); +#ifdef OBSOLETE /* remember stack location for reference + assignment * in lambda body. * */ env_stack_.top().alloc_var(arg_name, alloca); +#endif ++i; } } diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index c7f40362..cc1aaae3 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -9,21 +9,22 @@ namespace xo { using std::cerr; using std::endl; - llvm::AllocaInst * + int32_t activation_record::lookup_var(const std::string & x) const { - auto ix = frame_.find(x); + auto ix = name2ix_map_.find(x); - if (ix == frame_.end()) { + if (ix == name2ix_map_.end()) { cerr << "activation_record::lookup_var: no binding for variable x" << xtag("x", x) << endl; - return nullptr; + return -1; } return ix->second; } /*lookup_var*/ +#ifdef OBSOLETE llvm::AllocaInst * activation_record::alloc_var(const std::string & x, llvm::AllocaInst * alloca) @@ -38,6 +39,7 @@ namespace xo { frame_[x] = alloca; return alloca; } /*alloc_var*/ +#endif } /*namespace jit*/ } /*namespace xo*/ From 1ddc3e6a6269d95d9e7456d5ca7402b5a42f1f3b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 27 Jun 2024 17:46:08 -0400 Subject: [PATCH 1202/2524] xo-reflect: clang nit on FunctionTdxInfo --- include/xo/reflect/TypeDescr.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index a91befc8..95d9364c 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -119,7 +119,8 @@ namespace xo { /* hashable contents of a FunctionTdx instance (without requiring decl of TypeDescrExtra), * for unique-ification of manually-constructed function types */ - struct FunctionTdxInfo { + class FunctionTdxInfo { + public: FunctionTdxInfo() = default; FunctionTdxInfo(TypeDescr retval_td, const std::vector & arg_td_v, From d836f13b889d1b28230e0e3f312bdcc563dae00e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 30 Jun 2024 19:10:56 -0400 Subject: [PATCH 1203/2524] xo-expression: + environment implementation --- include/xo/expression/Environment.hpp | 23 +++++++++ include/xo/expression/GlobalEnv.hpp | 46 ++++++++++++++++++ include/xo/expression/LocalEnv.hpp | 70 +++++++++++++++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 include/xo/expression/Environment.hpp create mode 100644 include/xo/expression/GlobalEnv.hpp create mode 100644 include/xo/expression/LocalEnv.hpp diff --git a/include/xo/expression/Environment.hpp b/include/xo/expression/Environment.hpp new file mode 100644 index 00000000..834366c7 --- /dev/null +++ b/include/xo/expression/Environment.hpp @@ -0,0 +1,23 @@ +/* file Environment.hpp + * + * author: Roland Conybeare, Jun 2024 + */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include "Variable.hpp" + +namespace xo { + namespace ast { + class Environment : public ref::Refcount { + public: + /** lookup variable-expression @p vname in this environment + **/ + virtual ref::brw lookup_var(const std::string & vname) const = 0; + }; + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end Environment.hpp */ diff --git a/include/xo/expression/GlobalEnv.hpp b/include/xo/expression/GlobalEnv.hpp new file mode 100644 index 00000000..223fc9df --- /dev/null +++ b/include/xo/expression/GlobalEnv.hpp @@ -0,0 +1,46 @@ +/* file GlobalEnv.hpp + * + * author: Roland Conybeare, Jun 2024 + */ + +#pragma once + +#include "Environment.hpp" +#include + +namespace xo { + namespace ast { + class GlobalEnv : public Environment { + public: + ref::brw require_global(ref::brw var) { + const std::string & vname = var->name(); + + auto ix = var_map_.find(vname); + + if (ix == var_map_.end()) { + var_map_[vname] = var.get(); + return var; + } else { + return ix->second; + } + } /*require_global*/ + + // ----- Environment ----- + + virtual ref::brw lookup_var(const std::string & vname) const { + auto ix = var_map_.find(vname); + + if (ix == var_map_.end()) + return ref::brw::from_native(nullptr); + + return ix->second; + } + + private: + std::map> var_map_; + }; + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end GlobalEnv.hpp */ diff --git a/include/xo/expression/LocalEnv.hpp b/include/xo/expression/LocalEnv.hpp new file mode 100644 index 00000000..7f5f12dd --- /dev/null +++ b/include/xo/expression/LocalEnv.hpp @@ -0,0 +1,70 @@ +/* file LocalEnv.hpp + * + * author: Roland Conybeare, Jun 2024 + */ + +#pragma once + +#include "Environment.hpp" + +namespace xo { + namespace ast { + /** @brief LocalEnv + * + * @class Local environment for a lambda. + * Lists the Variables corresponding to this lambda's formal + * parameters, but also links to @ref Environment for + * innermost enclosing @ref Lambda. + **/ + class LocalEnv : public Environment { + public: + using TypeDescr = xo::reflect::TypeDescr; + + public: + /** named ctor idiom. Create instance with local variables per @p argv **/ + static ref::rp make(const std::vector> & argv) { + return new LocalEnv(argv); + } + + const std::vector> & argv() const { return argv_; } + int n_arg() const { return argv_.size(); } + TypeDescr fn_arg(uint32_t i) const { return argv_[i]->valuetype(); } + + /** single-assign this environment's parent **/ + void assign_parent(ref::brw p) { + assert(parent_env_.get() == nullptr); + parent_env_ = p.get(); + } + + // ----- Environment ----- + + virtual ref::brw lookup_var(const std::string & target) const override { + for (const auto & arg : argv_) { + if (arg->name() == target) + return arg; + } + + /* here: target not found in local vars, + * delegate to innermost ancestor + */ + return parent_env_->lookup_var(target); + } + + private: + LocalEnv(const std::vector> & argv) + : argv_(argv) {} + + private: + /** formal argument names **/ + std::vector> argv_; + + /** parent environment. Free variable in this lambda's + * body, will be resolved by referring them to @ref parent_env_. + **/ + ref::rp parent_env_; + }; + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end LocalEnv.hpp */ From a877af562ae1981214c8aca79df2137ea3347273 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 30 Jun 2024 19:11:40 -0400 Subject: [PATCH 1204/2524] xo-expression: + Expression::attach_envs() --- include/xo/expression/Apply.hpp | 7 ++++++ include/xo/expression/ConstantInterface.hpp | 6 +++++ include/xo/expression/Expression.hpp | 21 ++++++++++++++++ include/xo/expression/IfExpr.hpp | 14 +++++++++++ include/xo/expression/Lambda.hpp | 25 ++++++++++++++------ include/xo/expression/PrimitiveInterface.hpp | 2 ++ include/xo/expression/Variable.hpp | 7 ++++++ src/expression/Lambda.cpp | 14 ++++++----- 8 files changed, 83 insertions(+), 13 deletions(-) diff --git a/include/xo/expression/Apply.hpp b/include/xo/expression/Apply.hpp index b7cb8c31..666ca5c7 100644 --- a/include/xo/expression/Apply.hpp +++ b/include/xo/expression/Apply.hpp @@ -49,6 +49,13 @@ namespace xo { return n; } + virtual void attach_envs(ref::brw p) override { + fn_->attach_envs(p); + + for (const auto & arg : argv_) + arg->attach_envs(p); + } + virtual void display(std::ostream & os) const override; private: diff --git a/include/xo/expression/ConstantInterface.hpp b/include/xo/expression/ConstantInterface.hpp index 586322c7..cb38f50d 100644 --- a/include/xo/expression/ConstantInterface.hpp +++ b/include/xo/expression/ConstantInterface.hpp @@ -33,6 +33,12 @@ namespace xo { virtual TypeDescr value_td() const = 0; /** reflection-tagged pointer to literal value of this constant **/ virtual TaggedPtr value_tp() const = 0; + + // ----- Expression ----- + + virtual void attach_envs(ref::brw /*p*/) override {} + + }; /*ConstantInterface*/ } /*namespace ast*/ diff --git a/include/xo/expression/Expression.hpp b/include/xo/expression/Expression.hpp index b38eecbd..403c2403 100644 --- a/include/xo/expression/Expression.hpp +++ b/include/xo/expression/Expression.hpp @@ -9,9 +9,14 @@ #include "xo/refcnt/Refcounted.hpp" #include "exprtype.hpp" #include +#include namespace xo { namespace ast { + class Variable; /* see Variable.hpp */ + class Lambda; /* see Lamnbda.hpp */ + class Environment; /* see Environment.hpp */ + /** @class Expression * @brief abstract syntax tree for an EGAD program * @@ -47,6 +52,22 @@ namespace xo { **/ virtual std::size_t visit_preorder(VisitFn visitor_fn) = 0; + /** attach an environment to each lambda expression X in this subtree, + * that will: + * - resolve names matching X's arguments (formal parameters) to + * from @p X.argv + * - resolve free variables from @p parent + **/ + virtual void attach_envs(ref::brw parent) = 0; + + /** append to *p_set the set of free variables in this expression. + * returns the number of free variables introduced + * + * @param env stack of lexcically-enclosing lamnbda expressions, + * in nesting order, i.e. outermost first, innertmost last + **/ + //virtual std::int32_t find_free_vars(std::vector> env) = 0; + /** write human-readable representation to stream **/ virtual void display(std::ostream & os) const = 0; /** human-readable string representation **/ diff --git a/include/xo/expression/IfExpr.hpp b/include/xo/expression/IfExpr.hpp index 6f397b3d..f962c905 100644 --- a/include/xo/expression/IfExpr.hpp +++ b/include/xo/expression/IfExpr.hpp @@ -51,6 +51,20 @@ namespace xo { return n; } + virtual void attach_envs(ref::brw p) override { + test_->attach_envs(p); + when_true_->attach_envs(p); + when_false_->attach_envs(p); + } + +#ifdef NOT_USING + virtual std::int32_t find_free_vars(std::set> * p_set) override { + return (test_->find_free_vars(p_set) + + when_true_->find_free_vars(p_set) + + when_false_->find_free_vars(p_set)); + } +#endif + virtual void display(std::ostream & os) const override; private: diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index 20c4425b..791f3548 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -8,6 +8,7 @@ #include "Expression.hpp" #include "FunctionInterface.hpp" #include "Variable.hpp" +#include "LocalEnv.hpp" #include #include //#include @@ -35,16 +36,16 @@ namespace xo { } const std::string & type_str() const { return type_str_; } - const std::vector> & argv() const { return argv_; } + const std::vector> & argv() const { return local_env_->argv(); } const ref::rp & body() const { return body_; } // ----- FunctionInterface ----- virtual const std::string & name() const override { return name_; } /** return number of arguments expected by this function **/ - virtual int n_arg() const override { return argv_.size(); } + virtual int n_arg() const override { return local_env_->n_arg(); } virtual TypeDescr fn_retval() const override { return body_->valuetype(); } - virtual TypeDescr fn_arg(uint32_t i) const override { return argv_[i]->valuetype(); } + virtual TypeDescr fn_arg(uint32_t i) const override { return local_env_->fn_arg(i); } // ----- Expression ----- @@ -53,7 +54,7 @@ namespace xo { visitor_fn(this); - for (const auto & arg : argv_) + for (const auto & arg : local_env_->argv()) n += arg->visit_preorder(visitor_fn); n += body_->visit_preorder(visitor_fn); @@ -61,6 +62,10 @@ namespace xo { return n; } + virtual void attach_envs(ref::brw p) override { + local_env_->assign_parent(p); + } + virtual void display(std::ostream & os) const override; private: @@ -69,7 +74,7 @@ namespace xo { **/ Lambda(const std::string & name, TypeDescr lambda_type, - const std::vector> & argv, + const ref::rp & local_env, const ref::rp & body); private: @@ -85,10 +90,16 @@ namespace xo { * "double(double,double)" for function of two doubles that returns a double **/ std::string type_str_; - /** formal argument names **/ - std::vector> argv_; /** function body **/ ref::rp body_; + + /** established (once) by @ref attach_envs. + * + * @note data dependency on ancestor expressions that don't exist yet + * when Lambda constructor runs, so we need to assign @ref local_env_ + * later. + **/ + ref::rp local_env_; }; /*Lambda*/ inline ref::rp diff --git a/include/xo/expression/PrimitiveInterface.hpp b/include/xo/expression/PrimitiveInterface.hpp index 907aef2d..184a70d8 100644 --- a/include/xo/expression/PrimitiveInterface.hpp +++ b/include/xo/expression/PrimitiveInterface.hpp @@ -53,6 +53,8 @@ namespace xo { return 1; } + virtual void attach_envs(ref::brw /*p*/) override {} + private: }; /*PrimitiveInterface*/ } /*namespace ast*/ diff --git a/include/xo/expression/Variable.hpp b/include/xo/expression/Variable.hpp index 13e46a98..c4a2eb9a 100644 --- a/include/xo/expression/Variable.hpp +++ b/include/xo/expression/Variable.hpp @@ -36,6 +36,13 @@ namespace xo { return 1; } + virtual void attach_envs(ref::brw /*p*/) override {} + +#ifdef NOT_USING + virtual std::int32_t find_free_vars(std::set> * p_set) override { + } +#endif + virtual void display(std::ostream & os) const override; private: diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index 4cd25da9..7ad70ceb 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -41,23 +41,23 @@ namespace xo { return new Lambda(name, lambda_td, - argv, + LocalEnv::make(argv), body); } /*make*/ Lambda::Lambda(const std::string & name, TypeDescr lambda_type, - const std::vector> & argv, + const rp & local_env, const ref::rp & body) : FunctionInterface(exprtype::lambda, lambda_type), name_{name}, - argv_{argv}, - body_{body} + body_{body}, + local_env_{local_env} { stringstream ss; ss << "double"; ss << "("; - for (std::size_t i = 0; i < argv.size(); ++i) { + for (std::size_t i = 0, n = this->n_arg(); i < n; ++i) { if (i > 0) ss << ","; ss << "double"; @@ -65,13 +65,15 @@ namespace xo { ss << ")"; type_str_ = ss.str(); + + body_->attach_envs(local_env_); } /*ctor*/ void Lambda::display(std::ostream & os) const { os << "argv()) << xtag("body", body_) << ">"; } /*display*/ From 28884e1f4f97ed8cdde0c7ebcc1f20698c923963 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 30 Jun 2024 19:33:18 -0400 Subject: [PATCH 1205/2524] xo-expression: refactor: use GlobalEnv for MachPipeline::global_env --- include/xo/expression/Environment.hpp | 5 ++-- include/xo/expression/GlobalEnv.hpp | 34 +++++++++++++++------------ include/xo/expression/LocalEnv.hpp | 2 +- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/include/xo/expression/Environment.hpp b/include/xo/expression/Environment.hpp index 834366c7..6fec9b32 100644 --- a/include/xo/expression/Environment.hpp +++ b/include/xo/expression/Environment.hpp @@ -12,9 +12,10 @@ namespace xo { namespace ast { class Environment : public ref::Refcount { public: - /** lookup variable-expression @p vname in this environment + /** lookup variable-expression @p vname in this environment. + * returns llvm::Value representing code that produces a value for vname **/ - virtual ref::brw lookup_var(const std::string & vname) const = 0; + virtual ref::brw lookup_var(const std::string & vname) const = 0; }; } /*namespace ast*/ } /*namespace xo*/ diff --git a/include/xo/expression/GlobalEnv.hpp b/include/xo/expression/GlobalEnv.hpp index 223fc9df..fbc9ef9c 100644 --- a/include/xo/expression/GlobalEnv.hpp +++ b/include/xo/expression/GlobalEnv.hpp @@ -12,32 +12,36 @@ namespace xo { namespace ast { class GlobalEnv : public Environment { public: - ref::brw require_global(ref::brw var) { - const std::string & vname = var->name(); + /** create instance. Probably only need one of these **/ + static ref::rp make() { return new GlobalEnv(); } - auto ix = var_map_.find(vname); - - if (ix == var_map_.end()) { - var_map_[vname] = var.get(); - return var; - } else { - return ix->second; - } + ref::brw require_global(const std::string & vname, + ref::brw expr) { + global_map_[vname] = expr.get(); + return expr; } /*require_global*/ // ----- Environment ----- - virtual ref::brw lookup_var(const std::string & vname) const { - auto ix = var_map_.find(vname); + virtual ref::brw lookup_var(const std::string & vname) const { + auto ix = global_map_.find(vname); - if (ix == var_map_.end()) - return ref::brw::from_native(nullptr); + if (ix == global_map_.end()) { + /* not found */ + return ref::brw::from_native(nullptr); + } return ix->second; } private: - std::map> var_map_; + GlobalEnv() = default; + + private: + /* for assignable globals, need to allocate memory + * addresses for these. + */ + std::map> global_map_; }; } /*namespace ast*/ } /*namespace xo*/ diff --git a/include/xo/expression/LocalEnv.hpp b/include/xo/expression/LocalEnv.hpp index 7f5f12dd..b206bddf 100644 --- a/include/xo/expression/LocalEnv.hpp +++ b/include/xo/expression/LocalEnv.hpp @@ -38,7 +38,7 @@ namespace xo { // ----- Environment ----- - virtual ref::brw lookup_var(const std::string & target) const override { + virtual ref::brw lookup_var(const std::string & target) const override { for (const auto & arg : argv_) { if (arg->name() == target) return arg; From 1f0c0cb71de79e892fc3266c00203801b6871eca Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 30 Jun 2024 20:03:55 -0400 Subject: [PATCH 1206/2524] xo-jit: use Environment for toplevel lambdas --- include/xo/jit/MachPipeline.hpp | 4 +- include/xo/jit/activation_record.hpp | 27 +-- src/jit/MachPipeline.cpp | 261 +-------------------------- src/jit/activation_record.cpp | 10 +- 4 files changed, 16 insertions(+), 286 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 14003c44..11589e8c 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -20,6 +20,7 @@ #include "xo/expression/Lambda.hpp" #include "xo/expression/Variable.hpp" #include "xo/expression/IfExpr.hpp" +#include "xo/expression/GlobalEnv.hpp" /* stuff from kaleidoscope.cpp */ #include "llvm/ADT/APFloat.h" @@ -57,6 +58,7 @@ namespace xo { public: using Expression = xo::ast::Expression; using Lambda = xo::ast::Lambda; + using GlobalEnv = xo::ast::GlobalEnv; using TypeDescr = xo::reflect::TypeDescr; using ExecutionSession = llvm::orc::ExecutionSession; using DataLayout = llvm::DataLayout; @@ -211,7 +213,7 @@ namespace xo { std::unique_ptr llvm_module_; /** map global names to functions/variables **/ - std::map> global_env_; + ref::rp global_env_; /** map variable names (formal parameters) to * corresponding llvm IR. diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index 7c70ba3f..c2aba2dd 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -22,41 +22,16 @@ namespace xo { **/ class activation_record { public: - activation_record(llvm::Function * llvm_fn, - llvm::AllocaInst * frame) : frame_{frame} { - int i_arg = 0; - for (auto & arg : llvm_fn->args()) { - std::string arg_name = std::string(arg.getName()); + activation_record() = default; - name2ix_map_[arg_name] = 2 + i_arg; - } - } - - std::int32_t lookup_var(const std::string & var_name) const; - -#ifdef OBSOLETE llvm::AllocaInst * lookup_var(const std::string & var_name) const; llvm::AllocaInst * alloc_var(const std::string & var_name, llvm::AllocaInst * alloca); -#endif private: - /** stack frame for a user-defined function (lambda) **/ - llvm::AllocaInst * frame_ = nullptr; - - /** for each formal parameter, - * reports its position in stack frame. - * This is the position to use with getelementptr, - * i.e. +2 to skip first two slots, that are reserved - * for nextframe pointer (slot 0) + unwind pointer (slot 1) - **/ - std::map name2ix_map_; - -#ifdef OBSOLETE /** maps named slots in a stack frame to logical addresses **/ std::map frame_; /* <-> kaleidoscope NamedValues */ -#endif }; /*activation_record*/ } /*namespace jit*/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 76fa68ae..9c6bb1cd 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -13,6 +13,7 @@ namespace xo { using xo::ast::Variable; using xo::ast::Apply; using xo::ast::IfExpr; + using xo::ast::GlobalEnv; using xo::ast::llvmintrinsic; using xo::reflect::Reflect; using xo::reflect::StructMember; @@ -69,7 +70,8 @@ namespace xo { } /*make*/ MachPipeline::MachPipeline(std::unique_ptr jit) - : jit_{std::move(jit)} + : jit_{std::move(jit)}, + global_env_{GlobalEnv::make()} { this->recreate_llvm_ir_pipeline(); } @@ -627,42 +629,6 @@ namespace xo { } /*create_entry_block_alloca*/ - /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ - llvm::AllocaInst * - MachPipeline::create_entry_frame_alloca(llvm::Function * llvm_fn, - llvm::StructType * frame_llvm_type) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag), - xtag("llvm_fn", (void*)llvm_fn)); - - llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), - llvm_fn->getEntryBlock().begin()); - - if (!frame_llvm_type) - return nullptr; - - if (log) { - std::string llvm_frame_type_str; - llvm::raw_string_ostream ss(llvm_frame_type_str); - frame_llvm_type->print(ss); - - log(xtag("frame_llvm_type", llvm_frame_type_str)); - } - - llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(frame_llvm_type, - nullptr, - llvm_fn->getName()); - - log && log(xtag("alloca", (void*)retval), - xtag("align", retval->getAlign().value()), - xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); - - return retval; - } /*create_entry_frame_alloca*/ - std::vector> MachPipeline::find_lambdas(ref::brw expr) const { @@ -688,7 +654,7 @@ namespace xo { scope log(XO_DEBUG(c_debug_flag), xtag("lambda-name", lambda->name())); - global_env_[lambda->name()] = lambda.get(); + this->global_env_->require_global(lambda->name(), lambda); /* do we already know a function with this name? */ auto * fn = llvm_module_->getFunction(lambda->name()); @@ -736,175 +702,6 @@ namespace xo { return fn; } /*codegen_lambda_decl*/ - namespace { - /** A function type: - * - * _baseframe* (*) (_baseframe* - **/ - llvm::FunctionType * - require_baseframe_unwind_llvm_type(xo::ref::brw llvm_cx, - llvm::PointerType * frameptr_llvm_type) - { - std::vector llvm_argtype_v; - llvm_argtype_v.reserve(2); - - /* 1st arg is frame pointer */ - llvm_argtype_v.push_back(frameptr_llvm_type); - - /* 2nd arg is an i32. - * 0 -> unwind. - * 1 -> lift to heap (someday) - */ - llvm_argtype_v.push_back - (llvm::Type::getInt32Ty(llvm_cx->llvm_cx_ref())); - - /* return value is frame pointer */ - llvm::Type * retval_llvm_type = frameptr_llvm_type; - - auto * unwind_llvm_type = llvm::FunctionType::get(retval_llvm_type, - llvm_argtype_v, - false /*!varargs*/); - - return unwind_llvm_type; - } /*require_baseframe_unwind_llvm_type*/ - - /** Each lambda gets its own stack frame definition. - * However all the various frame representations share the same 'baseframe' - * prefix. - * - * _baseframe: - * ^ - * | - * +-------+ | - * next_frame [0] | o-------/ - * +-------| - * unwind_fn [1] | o-------> frame* (*)(frame*, ctl) - * +-------+ - * - * This helper function generates an llvm::Type* for a baseframe. - * It only needs to be invoked once (per LlvmContext, I guess ..) - **/ - llvm::StructType * - require_baseframe_llvm_type(xo::ref::brw llvm_cx) - { - /* _baseframe: base type for a stack frame */ - llvm::StructType * frame_llvm_type - = llvm::StructType::get(llvm_cx->llvm_cx_ref(), - "_baseframe"); - - /* _baseframe*: pointer to a stack frame */ - llvm::PointerType * frameptr_llvm_type - = llvm::PointerType::getUnqual(frame_llvm_type); - - /* unwind function = frame[1] */ - llvm::FunctionType * unwind_llvm_type - = require_baseframe_unwind_llvm_type(llvm_cx, - frameptr_llvm_type); - - /* _baseframe members */ - std::vector llvm_membertype_v; - { - llvm_membertype_v.reserve(2); - - /* frame[0] = pointer to next frame */ - llvm_membertype_v.push_back(frameptr_llvm_type); - /* frame[1] = unwind function */ - llvm_membertype_v.push_back(unwind_llvm_type); - } - - frame_llvm_type->setBody(frameptr_llvm_type /*frame[0]*/, - unwind_llvm_type /*frame[1]*/); - - return frame_llvm_type; - } /*require_baseframe_llvm_type*/ - - /** need a supporting type for stack frame - * - so we can handle variables with non-trivial dtors - * (e.g. smart pointers) - * - so we can implement nested lexical scoping - * - so we can walk stack for exception handling - * - eventually: so we can eventually implement trampoline - * - * frame representation: - * - * ^ - * | - * +-------+ | - * next_frame [0] | o-------/ - * +-------| - * unwind_fn [1] | o-------> baseframe* (*)(baseframe*, ctl) - * +-------| - * arg[i] [2+i] | ... | - * +-------+ - * - * invoke frame.unwind_fn to dispose of a frame - * - ctl=0 dtor. deal with smart pointers etc. - * - ctl=1 copy. lift frame into heap for lambda capture - * - * every frame is a subtype of _baseframe (ofc llvm doesn't know this). - * See baseframe_llvm_type(), baseframe_unwind_llvm_type() above - * - * editor bait: activation_record_to_llvm_type - **/ - llvm::StructType * - frame_to_llvm_type(xo::ref::brw llvm_cx, - ref::brw lambda, - llvm::Function * lambda_llvm_fn) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag)); - - /* frame type doesn't need a name */ - llvm::StructType * frame_llvm_type - = llvm::StructType::get(llvm_cx->llvm_cx_ref()); - - /* _baseframe */ - llvm::StructType * baseframe_llvm_type - = require_baseframe_llvm_type(llvm_cx); - - /* _baseframe*: pointer to a generic stack frame */ - llvm::PointerType * baseframeptr_llvm_type - = llvm::PointerType::getUnqual(baseframe_llvm_type); - - /* _baseframe* (*)(_baseframe*, i32) */ - llvm::FunctionType * unwind_llvm_type - = require_baseframe_unwind_llvm_type(llvm_cx, - baseframeptr_llvm_type); - - /* llvm_argtype_v: - * - llvm_argtype_v[0] = llvm::Type* for pointer to next_frame - * - llvm_argtype_v[1] = llvm::Type* for unwind_fn - * - llvm_argtype_v[2+i] = llvm::Type* for lambda->fn_arg(i) - */ - std::vector llvm_argtype_v; - { - llvm_argtype_v.reserve(2 + lambda_llvm_fn->arg_size()); - - /* frame pointer */ - llvm_argtype_v.push_back(baseframeptr_llvm_type); - /* unwind function */ - llvm_argtype_v.push_back(unwind_llvm_type); - - int i_arg = 0; - for (auto & arg : lambda_llvm_fn->args()) { - log && log(xtag("i_arg", i_arg), - xtag("param", std::string(arg.getName()))); - - llvm_argtype_v.push_back(td_to_llvm_type(llvm_cx, - lambda->fn_arg(i_arg))); - - ++i_arg; - } - } - - frame_llvm_type->setBody(llvm_argtype_v); - - return frame_llvm_type; - } /*frame_to_llvm_type*/ - } /*namespace*/ - llvm::Function * MachPipeline::codegen_lambda_defn(ref::brw lambda, llvm::IRBuilder<> & ir_builder) @@ -915,7 +712,7 @@ namespace xo { scope log(XO_DEBUG(c_debug_flag), xtag("lambda-name", lambda->name())); - global_env_[lambda->name()] = lambda.get(); + global_env_->require_global(lambda->name(), lambda.get()); /* do we already know a function with this name? */ auto * llvm_fn = llvm_module_->getFunction(lambda->name()); @@ -929,30 +726,17 @@ namespace xo { return nullptr; } + /* generate function body */ auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); ir_builder.SetInsertPoint(block); - /* create stack frame */ - llvm::StructType * frame_llvm_type - = frame_to_llvm_type(llvm_cx_, - lambda, - llvm_fn); - - llvm::AllocaInst * frame_alloca = create_entry_frame_alloca(llvm_fn, - frame_llvm_type); - - if (!frame_alloca) - return nullptr; - /** Actual parameters will need their own activation record. * Track its shape here. - * - * Local variables will be formal parameters to a nested lambda; **/ - this->env_stack_.push(activation_record(llvm_fn, frame_alloca)); + this->env_stack_.push(activation_record()); { log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); @@ -965,7 +749,6 @@ namespace xo { std::string arg_name = std::string(arg.getName()); -#ifdef OBSOLETE /* stack location for arg[i] */ llvm::AllocaInst * alloca = create_entry_block_alloca(llvm_fn, @@ -976,45 +759,17 @@ namespace xo { this->env_stack_.pop(); return nullptr; } -#endif - - /* need to store args to stack frame */ - - std::int32_t i_slot = env_stack_.top().lookup_var(arg_name); - - if (i_slot == -1) { - /* variable not found! */ - return nullptr; - } - - llvm::Value * i32_zero = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APInt(32, 0)); - llvm::Value * i32_slot = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APInt(32, i_slot)); - - std::array index_v = { - {i32_zero /*deref frame pointer*/, - i32_slot /*field# relative to frame pointer*/}}; - - /* location in stack frame of arg #i */ - auto arg_ptr - = ir_builder.CreateInBoundsGEP(frame_llvm_type, - frame_alloca, - index_v); /* store on function entry * see codegen_variable() for corresponding load */ - ir_builder.CreateStore(&arg, - alloca /*destination*/); + ir_builder.CreateStore(&arg, alloca); -#ifdef OBSOLETE /* remember stack location for reference + assignment * in lambda body. * */ env_stack_.top().alloc_var(arg_name, alloca); -#endif ++i; } } diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index cc1aaae3..c7f40362 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -9,22 +9,21 @@ namespace xo { using std::cerr; using std::endl; - int32_t + llvm::AllocaInst * activation_record::lookup_var(const std::string & x) const { - auto ix = name2ix_map_.find(x); + auto ix = frame_.find(x); - if (ix == name2ix_map_.end()) { + if (ix == frame_.end()) { cerr << "activation_record::lookup_var: no binding for variable x" << xtag("x", x) << endl; - return -1; + return nullptr; } return ix->second; } /*lookup_var*/ -#ifdef OBSOLETE llvm::AllocaInst * activation_record::alloc_var(const std::string & x, llvm::AllocaInst * alloca) @@ -39,7 +38,6 @@ namespace xo { frame_[x] = alloca; return alloca; } /*alloc_var*/ -#endif } /*namespace jit*/ } /*namespace xo*/ From b7db7c54549d6ad52ecbd1b4ac5feee8b9eeaac3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Jul 2024 16:50:17 -0400 Subject: [PATCH 1207/2524] xo-expression: + Expression::get_free_variables() --- include/xo/expression/Apply.hpp | 14 ++++++++++++++ include/xo/expression/ConstantInterface.hpp | 4 ++++ include/xo/expression/Expression.hpp | 6 ++++++ include/xo/expression/IfExpr.hpp | 15 +++++++++++++++ include/xo/expression/Lambda.hpp | 10 ++++++++++ include/xo/expression/PrimitiveInterface.hpp | 4 ++++ include/xo/expression/Variable.hpp | 6 ++++++ 7 files changed, 59 insertions(+) diff --git a/include/xo/expression/Apply.hpp b/include/xo/expression/Apply.hpp index 666ca5c7..b6db3a0c 100644 --- a/include/xo/expression/Apply.hpp +++ b/include/xo/expression/Apply.hpp @@ -36,6 +36,20 @@ namespace xo { const ref::rp & fn() const { return fn_; } const std::vector> & argv() const { return argv_; } + virtual std::set get_free_variables() const override { + std::set retval = fn_->get_free_variables(); + + for (const auto & arg : argv_) { + std::set arg_free_set + = arg->get_free_variables(); + + for (const auto & name : arg_free_set) + retval.insert(name); + } + + return retval; + } + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { std::size_t n = 1; diff --git a/include/xo/expression/ConstantInterface.hpp b/include/xo/expression/ConstantInterface.hpp index cb38f50d..0b10ec41 100644 --- a/include/xo/expression/ConstantInterface.hpp +++ b/include/xo/expression/ConstantInterface.hpp @@ -36,6 +36,10 @@ namespace xo { // ----- Expression ----- + virtual std::set get_free_variables() const override { + return std::set(); + } + virtual void attach_envs(ref::brw /*p*/) override {} diff --git a/include/xo/expression/Expression.hpp b/include/xo/expression/Expression.hpp index 403c2403..d23a412e 100644 --- a/include/xo/expression/Expression.hpp +++ b/include/xo/expression/Expression.hpp @@ -45,6 +45,12 @@ namespace xo { exprtype extype() const { return extype_; } TypeDescr valuetype() const { return valuetype_; } + /** find free named variables in this expression. + * comprises the set of names that don't match formal parameters in + * enclosing lambdas. + **/ + virtual std::set get_free_variables() const = 0; + /** visit each Expression node in this AST, * and invoke @p fn for each. * Returns the number of nodes visited. diff --git a/include/xo/expression/IfExpr.hpp b/include/xo/expression/IfExpr.hpp index f962c905..fc8204a7 100644 --- a/include/xo/expression/IfExpr.hpp +++ b/include/xo/expression/IfExpr.hpp @@ -40,6 +40,21 @@ namespace xo { // ----- Expression ----- + virtual std::set get_free_variables() const override { + std::set retval = test_->get_free_variables(); + + std::set free_vars; + free_vars = when_true_->get_free_variables(); + for (const auto & s : free_vars) + retval.insert(s); + + free_vars = when_false_->get_free_variables(); + for (const auto & s : free_vars) + retval.insert(s); + + return retval; + } + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { std::size_t n = 1; diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index 791f3548..a7f7529d 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -49,6 +49,16 @@ namespace xo { // ----- Expression ----- + virtual std::set get_free_variables() const override { + std::set retval = body_->get_free_variables(); + + /* but remove formals. */ + for (const auto & var : local_env_->argv()) + retval.erase(var->name()); + + return retval; + } + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { std::size_t n = 1; diff --git a/include/xo/expression/PrimitiveInterface.hpp b/include/xo/expression/PrimitiveInterface.hpp index 184a70d8..c52eb979 100644 --- a/include/xo/expression/PrimitiveInterface.hpp +++ b/include/xo/expression/PrimitiveInterface.hpp @@ -48,6 +48,10 @@ namespace xo { // ----- Expression ----- + virtual std::set get_free_variables() const override { + return std::set(); + } + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { visitor_fn(this); return 1; diff --git a/include/xo/expression/Variable.hpp b/include/xo/expression/Variable.hpp index c4a2eb9a..e8181a8c 100644 --- a/include/xo/expression/Variable.hpp +++ b/include/xo/expression/Variable.hpp @@ -31,6 +31,12 @@ namespace xo { const std::string & name() const { return name_; } + virtual std::set get_free_variables() const override { + std::set retval; + retval.insert(this->name_); + return retval; + } + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { visitor_fn(this); return 1; From 97264b726f2ec295bc548676ed186848d5013dbb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 1 Jul 2024 20:53:22 -0400 Subject: [PATCH 1208/2524] xo-expression: Lambda caches free var set + ::needs_closure() method --- include/xo/expression/Lambda.hpp | 16 +++++++++------- src/expression/Lambda.cpp | 16 +++++++++++++++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index a7f7529d..619913a1 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -39,6 +39,8 @@ namespace xo { const std::vector> & argv() const { return local_env_->argv(); } const ref::rp & body() const { return body_; } + bool needs_closure_flag() const { return !free_var_set_.empty(); } + // ----- FunctionInterface ----- virtual const std::string & name() const override { return name_; } @@ -50,13 +52,7 @@ namespace xo { // ----- Expression ----- virtual std::set get_free_variables() const override { - std::set retval = body_->get_free_variables(); - - /* but remove formals. */ - for (const auto & var : local_env_->argv()) - retval.erase(var->name()); - - return retval; + return this->free_var_set_; } virtual std::size_t visit_preorder(VisitFn visitor_fn) override { @@ -87,6 +83,9 @@ namespace xo { const ref::rp & local_env, const ref::rp & body); + /** compute free-variable set for this lambda **/ + std::set calc_free_variables() const; + private: /** lambda name. Initially supporting only form like * (define (foo x y z) @@ -103,6 +102,9 @@ namespace xo { /** function body **/ ref::rp body_; + /** free variables for this lambda **/ + std::set free_var_set_; + /** established (once) by @ref attach_envs. * * @note data dependency on ancestor expressions that don't exist yet diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index 7ad70ceb..d2fe8222 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -45,6 +45,19 @@ namespace xo { body); } /*make*/ + std::set + Lambda::calc_free_variables() const + { + std::set retval + = body_->get_free_variables(); + + /* but remove formals. */ + for (const auto & var : local_env_->argv()) + retval.erase(var->name()); + + return retval; + } /*calc_free_variables*/ + Lambda::Lambda(const std::string & name, TypeDescr lambda_type, const rp & local_env, @@ -64,7 +77,8 @@ namespace xo { } ss << ")"; - type_str_ = ss.str(); + this->type_str_ = ss.str(); + this->free_var_set_ = this->calc_free_variables(); body_->attach_envs(local_env_); } /*ctor*/ From 38ed17cd9f5297c05d1225ef817e0f1d372c6ee8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Jul 2024 12:25:18 -0400 Subject: [PATCH 1209/2524] + Expression.get_free_variables + fix primitives --- src/pyexpression/pyexpression.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index bc58917d..a5a931e4 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -55,6 +55,7 @@ namespace xo { py::class_>(m, "Expression") .def_property_readonly("extype", &Expression::extype) + .def("get_free_variables", &Expression::get_free_variables) .def("__repr__", &Expression::display_string); ; @@ -104,13 +105,13 @@ namespace xo { using Fn_dbl_dbl_type = double (*)(double); - m.def("make_sqrt_pm", []() { return make_primitive("sqrt", sqrt); }, + m.def("make_sqrt_pm", []() { return make_primitive("sqrt", sqrt, false /*!explicit*/, llvmintrinsic::invalid); }, py::doc("create primitive representing the ::sqrt() function")); - m.def("make_sin_pm", []() { return make_primitive("sin", ::sin); }, + m.def("make_sin_pm", []() { return make_primitive("sin", ::sin, false /*!explicit*/, llvmintrinsic::invalid); }, py::doc("create primitive representing the ::sin() function")); - m.def("make_cos_pm", []() { return make_primitive("cos", ::cos); }, + m.def("make_cos_pm", []() { return make_primitive("cos", ::cos, false /*!explicit*/, llvmintrinsic::invalid); }, py::doc("create primitive representing the ::cos() function")); - m.def("make_pow_pm", []() { return make_primitive("pow", ::pow); }, + m.def("make_pow_pm", []() { return make_primitive("pow", ::pow, false /*!explicit*/, llvmintrinsic::invalid); }, py::doc("create primitive representing the ::pow() function")); py::class_, From bf60c704da575c98faf0275ae401e0b1019a3141 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Jul 2024 14:19:57 -0400 Subject: [PATCH 1210/2524] xo-expression: + ptr to originating lambda --- include/xo/expression/LocalEnv.hpp | 22 ++++++++++++++++++++-- include/xo/expression/Variable.hpp | 5 ----- src/expression/Lambda.cpp | 16 ++++++++++++---- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/include/xo/expression/LocalEnv.hpp b/include/xo/expression/LocalEnv.hpp index b206bddf..2a7a3045 100644 --- a/include/xo/expression/LocalEnv.hpp +++ b/include/xo/expression/LocalEnv.hpp @@ -9,6 +9,8 @@ namespace xo { namespace ast { + class Lambda; + /** @brief LocalEnv * * @class Local environment for a lambda. @@ -26,10 +28,17 @@ namespace xo { return new LocalEnv(argv); } + Lambda * owner() const { return owner_; } const std::vector> & argv() const { return argv_; } int n_arg() const { return argv_.size(); } TypeDescr fn_arg(uint32_t i) const { return argv_[i]->valuetype(); } + /** single-assign this environment's owner **/ + void assign_owner(Lambda * p) { + assert(owner_ == nullptr); + owner_ = p; + } + /** single-assign this environment's parent **/ void assign_parent(ref::brw p) { assert(parent_env_.get() == nullptr); @@ -55,11 +64,20 @@ namespace xo { : argv_(argv) {} private: + /** Lambnda for which this environment created. + * + * Invariant: + * @code + * owner_->local_env_ == this + * @endcode + **/ + Lambda * owner_ = nullptr; + /** formal argument names **/ std::vector> argv_; - /** parent environment. Free variable in this lambda's - * body, will be resolved by referring them to @ref parent_env_. + /** parent environment. A free variable in this lambda's + * body will be resolved by referring them to @ref parent_env_. **/ ref::rp parent_env_; }; diff --git a/include/xo/expression/Variable.hpp b/include/xo/expression/Variable.hpp index e8181a8c..471eb873 100644 --- a/include/xo/expression/Variable.hpp +++ b/include/xo/expression/Variable.hpp @@ -44,11 +44,6 @@ namespace xo { virtual void attach_envs(ref::brw /*p*/) override {} -#ifdef NOT_USING - virtual std::int32_t find_free_vars(std::set> * p_set) override { - } -#endif - virtual void display(std::ostream & os) const override; private: diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index d2fe8222..6c8afafd 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -39,10 +39,18 @@ namespace xo { TypeDescr lambda_td = TypeDescrBase::require_by_fn_info(function_info); - return new Lambda(name, - lambda_td, - LocalEnv::make(argv), - body); + rp env = LocalEnv::make(argv); + + rp retval + = new Lambda(name, + lambda_td, + env, + body); + + /* need two-phase construction b/c pointer cycle */ + env->assign_owner(retval.get()); + + return retval; } /*make*/ std::set From a94c55304bbf57c4e9e89d09c532691207211b19 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Jul 2024 14:22:11 -0400 Subject: [PATCH 1211/2524] xo-expression: refactor: LocalEnv::owner -> origin --- include/xo/expression/LocalEnv.hpp | 14 +++++++------- src/expression/Lambda.cpp | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/xo/expression/LocalEnv.hpp b/include/xo/expression/LocalEnv.hpp index 2a7a3045..42a36f2e 100644 --- a/include/xo/expression/LocalEnv.hpp +++ b/include/xo/expression/LocalEnv.hpp @@ -28,15 +28,15 @@ namespace xo { return new LocalEnv(argv); } - Lambda * owner() const { return owner_; } + Lambda * origin() const { return origin_; } const std::vector> & argv() const { return argv_; } int n_arg() const { return argv_.size(); } TypeDescr fn_arg(uint32_t i) const { return argv_[i]->valuetype(); } - /** single-assign this environment's owner **/ - void assign_owner(Lambda * p) { - assert(owner_ == nullptr); - owner_ = p; + /** single-assign this environment's origin **/ + void assign_origin(Lambda * p) { + assert(origin_ == nullptr); + origin_ = p; } /** single-assign this environment's parent **/ @@ -68,10 +68,10 @@ namespace xo { * * Invariant: * @code - * owner_->local_env_ == this + * origin_->local_env_ == this * @endcode **/ - Lambda * owner_ = nullptr; + Lambda * origin_ = nullptr; /** formal argument names **/ std::vector> argv_; diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index 6c8afafd..2f84b178 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -48,7 +48,7 @@ namespace xo { body); /* need two-phase construction b/c pointer cycle */ - env->assign_owner(retval.get()); + env->assign_origin(retval.get()); return retval; } /*make*/ From 14796663b1d91a1593ee30c221281c635d2d0822 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 2 Jul 2024 16:57:07 -0400 Subject: [PATCH 1212/2524] xo-expression: + binding_path + assoc w/ each Variable --- include/xo/expression/Environment.hpp | 15 +++++++++++++ include/xo/expression/GlobalEnv.hpp | 12 +++++++++- include/xo/expression/LocalEnv.hpp | 6 ++++- include/xo/expression/Variable.hpp | 7 +++++- include/xo/expression/binding_path.hpp | 29 ++++++++++++++++++++++++ src/expression/CMakeLists.txt | 1 + src/expression/LocalEnv.cpp | 31 ++++++++++++++++++++++++++ src/expression/Variable.cpp | 7 ++++++ 8 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 include/xo/expression/binding_path.hpp create mode 100644 src/expression/LocalEnv.cpp diff --git a/include/xo/expression/Environment.hpp b/include/xo/expression/Environment.hpp index 6fec9b32..6baded6d 100644 --- a/include/xo/expression/Environment.hpp +++ b/include/xo/expression/Environment.hpp @@ -12,6 +12,21 @@ namespace xo { namespace ast { class Environment : public ref::Refcount { public: + /** true if this is toplevel (global) environment. + * Toplevel environment doesn't have slot numbers. + * + * Variables that bind in the global environment have unique + * names, which we rely on instead of slot numbers. + **/ + virtual bool is_global_env() const = 0; + + /** lookup binding path for @p vname in this environment. + * + * Reports ingredients needed to address variable at runtime, + * in runtime analog of this environment + **/ + virtual binding_path lookup_binding(const std::string & vname) const = 0; + /** lookup variable-expression @p vname in this environment. * returns llvm::Value representing code that produces a value for vname **/ diff --git a/include/xo/expression/GlobalEnv.hpp b/include/xo/expression/GlobalEnv.hpp index fbc9ef9c..08eb9355 100644 --- a/include/xo/expression/GlobalEnv.hpp +++ b/include/xo/expression/GlobalEnv.hpp @@ -7,6 +7,7 @@ #include "Environment.hpp" #include +#include namespace xo { namespace ast { @@ -23,7 +24,16 @@ namespace xo { // ----- Environment ----- - virtual ref::brw lookup_var(const std::string & vname) const { + virtual bool is_global_env() const override { return true; } + + virtual binding_path lookup_binding(const std::string & vname) const override { + /* i_link: -1 for global environment + * j_slot: not used + */ + return { -1, 0 }; + } + + virtual ref::brw lookup_var(const std::string & vname) const override { auto ix = global_map_.find(vname); if (ix == global_map_.end()) { diff --git a/include/xo/expression/LocalEnv.hpp b/include/xo/expression/LocalEnv.hpp index 42a36f2e..d8fe2178 100644 --- a/include/xo/expression/LocalEnv.hpp +++ b/include/xo/expression/LocalEnv.hpp @@ -47,6 +47,10 @@ namespace xo { // ----- Environment ----- + virtual bool is_global_env() const override { return false; } + + virtual binding_path lookup_binding(const std::string & vname) const override; + virtual ref::brw lookup_var(const std::string & target) const override { for (const auto & arg : argv_) { if (arg->name() == target) @@ -61,7 +65,7 @@ namespace xo { private: LocalEnv(const std::vector> & argv) - : argv_(argv) {} + : origin_{nullptr}, argv_(argv) {} private: /** Lambnda for which this environment created. diff --git a/include/xo/expression/Variable.hpp b/include/xo/expression/Variable.hpp index 471eb873..3f45677e 100644 --- a/include/xo/expression/Variable.hpp +++ b/include/xo/expression/Variable.hpp @@ -6,6 +6,7 @@ #pragma once #include "Expression.hpp" +#include "binding_path.hpp" namespace xo { namespace ast { @@ -42,7 +43,7 @@ namespace xo { return 1; } - virtual void attach_envs(ref::brw /*p*/) override {} + virtual void attach_envs(ref::brw /*p*/) override; virtual void display(std::ostream & os) const override; @@ -55,6 +56,10 @@ namespace xo { private: /** variable name **/ std::string name_; + /** navigate environment via this path to find runtime memory + * location for this variable + **/ + binding_path path_; }; /*Variable*/ inline ref::rp diff --git a/include/xo/expression/binding_path.hpp b/include/xo/expression/binding_path.hpp new file mode 100644 index 00000000..a3692ed1 --- /dev/null +++ b/include/xo/expression/binding_path.hpp @@ -0,0 +1,29 @@ +/* file binding_path.hpp + * + * author: Roland Conybeare, Jul 2024 + */ + +#pragma once + +namespace xo { + namespace ast { + /** @class path + * + * @brief path from the *use* of a variable to the environment + * providing its location. + **/ + struct binding_path { + /** @of parent links to traverse. -1 if global **/ + int i_link_ = -1; + /** for variables bound in some local environment: + * slot# within that environment. + * + * Ignored if @ref i_link_ is -1 + **/ + int j_slot_ = 0; + }; /*binding_path*/ + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end binding_path.hpp */ diff --git a/src/expression/CMakeLists.txt b/src/expression/CMakeLists.txt index ef5cf6f2..50edec01 100644 --- a/src/expression/CMakeLists.txt +++ b/src/expression/CMakeLists.txt @@ -7,6 +7,7 @@ set(SELF_SRCS Lambda.cpp Variable.cpp IfExpr.cpp + LocalEnv.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) diff --git a/src/expression/LocalEnv.cpp b/src/expression/LocalEnv.cpp new file mode 100644 index 00000000..fb724aeb --- /dev/null +++ b/src/expression/LocalEnv.cpp @@ -0,0 +1,31 @@ +/* file LocalEnv.cpp + * + * author: Roland Conybeare + */ + +#include "LocalEnv.hpp" + +namespace xo { + namespace ast { + binding_path + LocalEnv::lookup_binding(const std::string & vname) const + { + int j_slot = 0; + for (const auto & arg : argv_) { + if (arg->name() == vname) + return { 0 /*i_link*/, j_slot }; + ++j_slot; + } + + auto tmp = parent_env_->lookup_binding(vname); + + if (tmp.i_link_ == -1) + return tmp; + else + return { tmp.i_link_ + 1, tmp.j_slot_ }; + } /*lookup_binding*/ + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end LocalEnv.cpp */ diff --git a/src/expression/Variable.cpp b/src/expression/Variable.cpp index 52890ccc..71d47655 100644 --- a/src/expression/Variable.cpp +++ b/src/expression/Variable.cpp @@ -1,9 +1,16 @@ /* @file Variable.cpp */ #include "Variable.hpp" +#include "Environment.hpp" namespace xo { namespace ast { + void + Variable::attach_envs(ref::brw e) { + /** e makes accessible all enclosing lexical scopes **/ + this->path_ = e->lookup_binding(this->name_); + } /*attach_envs*/ + void Variable::display(std::ostream & os) const { os << " Date: Wed, 3 Jul 2024 14:11:02 -0400 Subject: [PATCH 1213/2524] xo-exprssion: + Expression::xform_layer() --- include/xo/expression/Apply.hpp | 9 ++++++ include/xo/expression/Constant.hpp | 4 +++ include/xo/expression/Expression.hpp | 8 ++++- include/xo/expression/IfExpr.hpp | 9 ++++++ include/xo/expression/Lambda.hpp | 5 +++ include/xo/expression/PrimitiveInterface.hpp | 4 +++ include/xo/expression/Variable.hpp | 4 +++ src/expression/Lambda.cpp | 33 ++++++++++++++++++-- 8 files changed, 72 insertions(+), 4 deletions(-) diff --git a/include/xo/expression/Apply.hpp b/include/xo/expression/Apply.hpp index b6db3a0c..1ed06e6b 100644 --- a/include/xo/expression/Apply.hpp +++ b/include/xo/expression/Apply.hpp @@ -63,6 +63,15 @@ namespace xo { return n; } + virtual ref::rp xform_layer(TransformFn xform_fn) override { + this->fn_ = fn_->xform_layer(xform_fn); + + for (auto & arg : argv_) + arg = arg->xform_layer(xform_fn); + + return xform_fn(this); + } + virtual void attach_envs(ref::brw p) override { fn_->attach_envs(p); diff --git a/include/xo/expression/Constant.hpp b/include/xo/expression/Constant.hpp index 50f4c2ba..ffc15967 100644 --- a/include/xo/expression/Constant.hpp +++ b/include/xo/expression/Constant.hpp @@ -56,6 +56,10 @@ namespace xo { return 1; } + virtual ref::rp xform_layer(TransformFn xform_fn) override { + return xform_fn(this); + } + virtual void display(std::ostream & os) const override { os << "short_name()) diff --git a/include/xo/expression/Expression.hpp b/include/xo/expression/Expression.hpp index d23a412e..84248555 100644 --- a/include/xo/expression/Expression.hpp +++ b/include/xo/expression/Expression.hpp @@ -35,7 +35,10 @@ namespace xo { **/ class Expression : public ref::Refcount { public: - using VisitFn = std::function)>; + using VisitFn = std::function + )>; + using TransformFn = std::function + (ref::brw)>; using TypeDescr = xo::reflect::TypeDescr; public: @@ -58,6 +61,9 @@ namespace xo { **/ virtual std::size_t visit_preorder(VisitFn visitor_fn) = 0; + /** traverse ast @ref visit_preorder but do not visit Lambdas **/ + virtual ref::rp xform_layer(TransformFn visitor_fn) = 0; + /** attach an environment to each lambda expression X in this subtree, * that will: * - resolve names matching X's arguments (formal parameters) to diff --git a/include/xo/expression/IfExpr.hpp b/include/xo/expression/IfExpr.hpp index fc8204a7..8091481e 100644 --- a/include/xo/expression/IfExpr.hpp +++ b/include/xo/expression/IfExpr.hpp @@ -60,12 +60,21 @@ namespace xo { visitor_fn(this); + n += this->test_->visit_preorder(visitor_fn); n += this->when_true_->visit_preorder(visitor_fn); n += this->when_false_->visit_preorder(visitor_fn); return n; } + virtual ref::rp xform_layer(TransformFn xform_fn) override { + this->test_ = this->test_->xform_layer(xform_fn); + this->when_true_ = this->when_true_->xform_layer(xform_fn); + this->when_false_= this->when_false_->xform_layer(xform_fn); + + return xform_fn(this); + } + virtual void attach_envs(ref::brw p) override { test_->attach_envs(p); when_true_->attach_envs(p); diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index 619913a1..18039982 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -68,6 +68,11 @@ namespace xo { return n; } + virtual ref::rp xform_layer(TransformFn /*xform_fn*/) override { + /* a layer is bounded by lambdas, don't enter them */ + return this; + } + virtual void attach_envs(ref::brw p) override { local_env_->assign_parent(p); } diff --git a/include/xo/expression/PrimitiveInterface.hpp b/include/xo/expression/PrimitiveInterface.hpp index c52eb979..70080607 100644 --- a/include/xo/expression/PrimitiveInterface.hpp +++ b/include/xo/expression/PrimitiveInterface.hpp @@ -57,6 +57,10 @@ namespace xo { return 1; } + virtual ref::rp xform_layer(TransformFn xform_fn) override { + return xform_fn(this); + } + virtual void attach_envs(ref::brw /*p*/) override {} private: diff --git a/include/xo/expression/Variable.hpp b/include/xo/expression/Variable.hpp index 3f45677e..713da66f 100644 --- a/include/xo/expression/Variable.hpp +++ b/include/xo/expression/Variable.hpp @@ -43,6 +43,10 @@ namespace xo { return 1; } + virtual ref::rp xform_layer(TransformFn xform_fn) override { + return xform_fn(this); + } + virtual void attach_envs(ref::brw /*p*/) override; virtual void display(std::ostream & os) const override; diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index 2f84b178..b2e4d1f3 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -4,6 +4,7 @@ #include "xo/reflect/TypeDescr.hpp" #include "xo/reflect/function/FunctionTdx.hpp" #include "xo/indentlog/print/vector.hpp" +#include namespace xo { using xo::reflect::TypeDescrBase; @@ -25,10 +26,12 @@ namespace xo { **/ std::vector arg_td_v; - arg_td_v.reserve(argv.size()); + { + arg_td_v.reserve(argv.size()); - for (const auto & arg : argv) { - arg_td_v.push_back(arg->valuetype()); + for (const auto & arg : argv) { + arg_td_v.push_back(arg->valuetype()); + } } auto function_info @@ -85,6 +88,30 @@ namespace xo { } ss << ")"; + /* regularize local_env+body: make sure exactly one instance + * (i.e. with object identity) of a Variable appears + * within one layer of a lambda body. + * + * Here 'layer' means excluding appearance in any nested lambdas + * (i.e. whether or not such appearance would resolve to the same + * memory location). + * + * Motivation is to unify Variables that would use the same + * binding_path to resolve their runtime location. + */ + { + std::map> var_map; + + for (const auto & arg : local_env_->argv()) { + /* each arg name can appear at most once + * in a particular lambda's parameter list + */ + assert(var_map.find(arg->name()) == var_map.end()); + + var_map[arg->name()] = arg; + } + } + this->type_str_ = ss.str(); this->free_var_set_ = this->calc_free_variables(); From fdfe2e7270e32c735b6ad8e8d657807f81fc4a8d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Jul 2024 14:31:47 -0400 Subject: [PATCH 1214/2524] xo-expression: unify variables within each lambda layer --- src/expression/Lambda.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index b2e4d1f3..30b579cf 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -110,6 +110,28 @@ namespace xo { var_map[arg->name()] = arg; } + + body_ = body_->xform_layer + ([&var_map](ref::brw x) -> ref::rp + { + if (x->extype() == exprtype::variable) { + ref::brw var = Variable::from(x); + + auto ix = var_map.find(var->name()); + if (ix == var_map.end()) { + /* add to var_map */ + + var_map[var->name()] = var.get(); + + return var.get(); + } else { + /* substitute already-encountered var_map[] member */ + return ix->second; + } + } else { + return x.get(); + } + }); } this->type_str_ = ss.str(); From a76c8354774ea9929b1b68ee74b0c8e034c88712 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Jul 2024 14:39:32 -0400 Subject: [PATCH 1215/2524] xo-expression: refactor: xtract -> method regularize_layer_vars() --- include/xo/expression/Lambda.hpp | 8 ++++++ src/expression/Lambda.cpp | 44 ++++++++++++++++++-------------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index 18039982..d4ec9c54 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -91,6 +91,14 @@ namespace xo { /** compute free-variable set for this lambda **/ std::set calc_free_variables() const; + /** ensure at most one Variable instance with a particular name + * in this lambda, but ignore nested lambdas. + * + * Goal is to unify variables that can use the same binding + * path to determine memory location at runtime. + **/ + void regularize_layer_vars(); + private: /** lambda name. Initially supporting only form like * (define (foo x y z) diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index 30b579cf..833c998f 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -69,25 +69,9 @@ namespace xo { return retval; } /*calc_free_variables*/ - Lambda::Lambda(const std::string & name, - TypeDescr lambda_type, - const rp & local_env, - const ref::rp & body) - : FunctionInterface(exprtype::lambda, lambda_type), - name_{name}, - body_{body}, - local_env_{local_env} + void + Lambda::regularize_layer_vars() { - stringstream ss; - ss << "double"; - ss << "("; - for (std::size_t i = 0, n = this->n_arg(); i < n; ++i) { - if (i > 0) - ss << ","; - ss << "double"; - } - ss << ")"; - /* regularize local_env+body: make sure exactly one instance * (i.e. with object identity) of a Variable appears * within one layer of a lambda body. @@ -133,11 +117,33 @@ namespace xo { } }); } + } /*regularize_layer_vars*/ + + Lambda::Lambda(const std::string & name, + TypeDescr lambda_type, + const rp & local_env, + const ref::rp & body) + : FunctionInterface(exprtype::lambda, lambda_type), + name_{name}, + body_{body}, + local_env_{local_env} + { + stringstream ss; + ss << "double"; + ss << "("; + for (std::size_t i = 0, n = this->n_arg(); i < n; ++i) { + if (i > 0) + ss << ","; + ss << "double"; + } + ss << ")"; this->type_str_ = ss.str(); this->free_var_set_ = this->calc_free_variables(); - body_->attach_envs(local_env_); + this->regularize_layer_vars(); + + this->body_->attach_envs(local_env_); } /*ctor*/ void From f18c33b24937f66d857fe098887bb08991bea721 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Jul 2024 14:43:34 -0400 Subject: [PATCH 1216/2524] xo-expression: bugfix: ensure in-layer uniqueness of vars --- include/xo/expression/Variable.hpp | 5 +++++ src/expression/Lambda.cpp | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/xo/expression/Variable.hpp b/include/xo/expression/Variable.hpp index 713da66f..8ca2cb2b 100644 --- a/include/xo/expression/Variable.hpp +++ b/include/xo/expression/Variable.hpp @@ -25,6 +25,11 @@ namespace xo { return new Variable(name, var_type); } + /** return copy of x: same var, different object identity **/ + static ref::rp copy(ref::brw x) { + return new Variable(x->name(), x->valuetype()); + } + /** downcast from Expression **/ static ref::brw from(ref::brw x) { return ref::brw::from(x); diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index 833c998f..72459fe5 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -103,9 +103,11 @@ namespace xo { auto ix = var_map.find(var->name()); if (ix == var_map.end()) { - /* add to var_map */ + /* add to var_map, copy to ensure Variable + * is unique to layer + */ - var_map[var->name()] = var.get(); + var_map[var->name()] = Variable::copy(var); return var.get(); } else { From 89043b0d46880440d3c51a8b19ca4e0dd7014bd9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Jul 2024 15:10:34 -0400 Subject: [PATCH 1217/2524] xo-expression: + Lambda::layer_var_map --- include/xo/expression/Lambda.hpp | 17 +++++-- src/expression/Lambda.cpp | 81 ++++++++++++++++++-------------- 2 files changed, 58 insertions(+), 40 deletions(-) diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index d4ec9c54..231e2043 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -9,6 +9,7 @@ #include "FunctionInterface.hpp" #include "Variable.hpp" #include "LocalEnv.hpp" +#include #include #include //#include @@ -73,9 +74,7 @@ namespace xo { return this; } - virtual void attach_envs(ref::brw p) override { - local_env_->assign_parent(p); - } + virtual void attach_envs(ref::brw p) override; virtual void display(std::ostream & os) const override; @@ -97,7 +96,7 @@ namespace xo { * Goal is to unify variables that can use the same binding * path to determine memory location at runtime. **/ - void regularize_layer_vars(); + std::map> regularize_layer_vars(); private: /** lambda name. Initially supporting only form like @@ -118,6 +117,16 @@ namespace xo { /** free variables for this lambda **/ std::set free_var_set_; + /** map giving unique identity to each variable appearing in this layer. + * includes: + * - formal parameters + * - free variables in @ref body_ + * excludes: + * - any variables appearing in nested lambdas + * (whether formals or free variables) + **/ + std::map> layer_var_map_; + /** established (once) by @ref attach_envs. * * @note data dependency on ancestor expressions that don't exist yet diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index 72459fe5..85f8bbcf 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -69,7 +69,7 @@ namespace xo { return retval; } /*calc_free_variables*/ - void + std::map> Lambda::regularize_layer_vars() { /* regularize local_env+body: make sure exactly one instance @@ -83,42 +83,43 @@ namespace xo { * Motivation is to unify Variables that would use the same * binding_path to resolve their runtime location. */ - { - std::map> var_map; + std::map> var_map; - for (const auto & arg : local_env_->argv()) { - /* each arg name can appear at most once - * in a particular lambda's parameter list - */ - assert(var_map.find(arg->name()) == var_map.end()); + for (const auto & arg : local_env_->argv()) { + /* each arg name can appear at most once + * in a particular lambda's parameter list + */ + assert(var_map.find(arg->name()) == var_map.end()); - var_map[arg->name()] = arg; - } - - body_ = body_->xform_layer - ([&var_map](ref::brw x) -> ref::rp - { - if (x->extype() == exprtype::variable) { - ref::brw var = Variable::from(x); - - auto ix = var_map.find(var->name()); - if (ix == var_map.end()) { - /* add to var_map, copy to ensure Variable - * is unique to layer - */ - - var_map[var->name()] = Variable::copy(var); - - return var.get(); - } else { - /* substitute already-encountered var_map[] member */ - return ix->second; - } - } else { - return x.get(); - } - }); + var_map[arg->name()] = arg; } + + this->body_ + = (body_->xform_layer + ([&var_map](ref::brw x) -> ref::rp + { + if (x->extype() == exprtype::variable) { + ref::brw var = Variable::from(x); + + auto ix = var_map.find(var->name()); + if (ix == var_map.end()) { + /* add to var_map, copy to ensure Variable + * not shared with any other layer + */ + + var_map[var->name()] = Variable::copy(var); + + return var.get(); + } else { + /* substitute already-encountered var_map[] member */ + return ix->second; + } + } else { + return x.get(); + } + })); + + return var_map; } /*regularize_layer_vars*/ Lambda::Lambda(const std::string & name, @@ -141,13 +142,21 @@ namespace xo { ss << ")"; this->type_str_ = ss.str(); - this->free_var_set_ = this->calc_free_variables(); - this->regularize_layer_vars(); + /* ensure variables are unique within layer for this lambda */ + this->layer_var_map_ = this->regularize_layer_vars(); + + this->free_var_set_ = this->calc_free_variables(); this->body_->attach_envs(local_env_); } /*ctor*/ + void + Lambda::attach_envs(ref::brw p) { + local_env_->assign_parent(p); + + } + void Lambda::display(std::ostream & os) const { os << " Date: Wed, 3 Jul 2024 15:43:43 -0400 Subject: [PATCH 1218/2524] xo-reflect: + Borrow::operator=() --- include/xo/refcnt/Refcounted.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/include/xo/refcnt/Refcounted.hpp b/include/xo/refcnt/Refcounted.hpp index da4ba539..0b62e557 100644 --- a/include/xo/refcnt/Refcounted.hpp +++ b/include/xo/refcnt/Refcounted.hpp @@ -297,8 +297,11 @@ namespace xo { return ptrdiff_t(x.get() - y.get()); } /*compare*/ - private: - Borrow(T * x) : ptr_(x) {} + template + Borrow & operator=(const Borrow & x) { + ptr_ = x.get(); + return *this; + } private: T * ptr_ = nullptr; From b6ba7615513b0913c725e5690a4317676ad6ee03 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Jul 2024 15:45:37 -0400 Subject: [PATCH 1219/2524] xo-expression: Borrow ctor --- include/xo/refcnt/Refcounted.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/xo/refcnt/Refcounted.hpp b/include/xo/refcnt/Refcounted.hpp index 0b62e557..33c5b377 100644 --- a/include/xo/refcnt/Refcounted.hpp +++ b/include/xo/refcnt/Refcounted.hpp @@ -257,6 +257,8 @@ namespace xo { template class Borrow { public: + Borrow() = default; + template Borrow(rp const & x) : ptr_(x.get()) {} From 91a5a2b844ea70d9078abf14f367d75929c0e40a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Jul 2024 16:18:26 -0400 Subject: [PATCH 1220/2524] xo-expression: + Expression::nested_layer() --- include/xo/expression/Apply.hpp | 13 +++++++++++++ include/xo/expression/Constant.hpp | 5 +++++ include/xo/expression/Environment.hpp | 4 +++- include/xo/expression/Expression.hpp | 7 +++++++ include/xo/expression/IfExpr.hpp | 12 ++++++++++++ include/xo/expression/Lambda.hpp | 8 ++++++++ include/xo/expression/LocalEnv.hpp | 2 ++ include/xo/expression/PrimitiveInterface.hpp | 5 +++++ include/xo/expression/Variable.hpp | 5 +++++ src/expression/Lambda.cpp | 2 +- 10 files changed, 61 insertions(+), 2 deletions(-) diff --git a/include/xo/expression/Apply.hpp b/include/xo/expression/Apply.hpp index 1ed06e6b..e6de3ac4 100644 --- a/include/xo/expression/Apply.hpp +++ b/include/xo/expression/Apply.hpp @@ -63,6 +63,19 @@ namespace xo { return n; } + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += fn_->visit_layer(visitor_fn); + + for (const auto & arg : argv_) + n += arg->visit_layer(visitor_fn); + + return n; + } + virtual ref::rp xform_layer(TransformFn xform_fn) override { this->fn_ = fn_->xform_layer(xform_fn); diff --git a/include/xo/expression/Constant.hpp b/include/xo/expression/Constant.hpp index ffc15967..3ecf1fe3 100644 --- a/include/xo/expression/Constant.hpp +++ b/include/xo/expression/Constant.hpp @@ -56,6 +56,11 @@ namespace xo { return 1; } + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + visitor_fn(this); + return 1; + } + virtual ref::rp xform_layer(TransformFn xform_fn) override { return xform_fn(this); } diff --git a/include/xo/expression/Environment.hpp b/include/xo/expression/Environment.hpp index 6baded6d..b827a575 100644 --- a/include/xo/expression/Environment.hpp +++ b/include/xo/expression/Environment.hpp @@ -6,10 +6,12 @@ #pragma once #include "xo/refcnt/Refcounted.hpp" -#include "Variable.hpp" +#include "binding_path.hpp" namespace xo { namespace ast { + class Expression; + class Environment : public ref::Refcount { public: /** true if this is toplevel (global) environment. diff --git a/include/xo/expression/Expression.hpp b/include/xo/expression/Expression.hpp index 84248555..89a876d4 100644 --- a/include/xo/expression/Expression.hpp +++ b/include/xo/expression/Expression.hpp @@ -61,6 +61,13 @@ namespace xo { **/ virtual std::size_t visit_preorder(VisitFn visitor_fn) = 0; + /** visit each Expression node in this AST, + * including immediately-nested Lambda nodes; + * but do not recurse into the params/body of such nested Lambdas. + * Returns the number of nodes visited + **/ + virtual std::size_t visit_layer(VisitFn visitor_fn) = 0; + /** traverse ast @ref visit_preorder but do not visit Lambdas **/ virtual ref::rp xform_layer(TransformFn visitor_fn) = 0; diff --git a/include/xo/expression/IfExpr.hpp b/include/xo/expression/IfExpr.hpp index 8091481e..bfe1e116 100644 --- a/include/xo/expression/IfExpr.hpp +++ b/include/xo/expression/IfExpr.hpp @@ -67,6 +67,18 @@ namespace xo { return n; } + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += this->test_->visit_layer(visitor_fn); + n += this->when_true_->visit_layer(visitor_fn); + n += this->when_false_->visit_layer(visitor_fn); + + return n; + } + virtual ref::rp xform_layer(TransformFn xform_fn) override { this->test_ = this->test_->xform_layer(xform_fn); this->when_true_ = this->when_true_->xform_layer(xform_fn); diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index 231e2043..3a3de6ac 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -69,6 +69,14 @@ namespace xo { return n; } + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + return n; + } + virtual ref::rp xform_layer(TransformFn /*xform_fn*/) override { /* a layer is bounded by lambdas, don't enter them */ return this; diff --git a/include/xo/expression/LocalEnv.hpp b/include/xo/expression/LocalEnv.hpp index d8fe2178..3106dc6c 100644 --- a/include/xo/expression/LocalEnv.hpp +++ b/include/xo/expression/LocalEnv.hpp @@ -6,6 +6,8 @@ #pragma once #include "Environment.hpp" +#include "Variable.hpp" +#include "xo/reflect/TypeDescr.hpp" namespace xo { namespace ast { diff --git a/include/xo/expression/PrimitiveInterface.hpp b/include/xo/expression/PrimitiveInterface.hpp index 70080607..12d4c4a7 100644 --- a/include/xo/expression/PrimitiveInterface.hpp +++ b/include/xo/expression/PrimitiveInterface.hpp @@ -57,6 +57,11 @@ namespace xo { return 1; } + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + visitor_fn(this); + return 1; + } + virtual ref::rp xform_layer(TransformFn xform_fn) override { return xform_fn(this); } diff --git a/include/xo/expression/Variable.hpp b/include/xo/expression/Variable.hpp index 8ca2cb2b..7d965dea 100644 --- a/include/xo/expression/Variable.hpp +++ b/include/xo/expression/Variable.hpp @@ -48,6 +48,11 @@ namespace xo { return 1; } + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + visitor_fn(this); + return 1; + } + virtual ref::rp xform_layer(TransformFn xform_fn) override { return xform_fn(this); } diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index 85f8bbcf..fba94694 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -112,7 +112,7 @@ namespace xo { return var.get(); } else { /* substitute already-encountered var_map[] member */ - return ix->second; + return ix->second.get(); } } else { return x.get(); From 4b0a2cff2a7413c1662ec8d3d20404347d107538 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Jul 2024 16:20:18 -0400 Subject: [PATCH 1221/2524] xo-expression: + Lambda::nested_lambda_map --- include/xo/expression/GlobalEnv.hpp | 2 +- include/xo/expression/Lambda.hpp | 3 +++ include/xo/expression/binding_path.hpp | 4 ++-- src/expression/Lambda.cpp | 21 ++++++++++++++++++++- src/expression/Variable.cpp | 6 +++++- 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/include/xo/expression/GlobalEnv.hpp b/include/xo/expression/GlobalEnv.hpp index 08eb9355..010dc296 100644 --- a/include/xo/expression/GlobalEnv.hpp +++ b/include/xo/expression/GlobalEnv.hpp @@ -26,7 +26,7 @@ namespace xo { virtual bool is_global_env() const override { return true; } - virtual binding_path lookup_binding(const std::string & vname) const override { + virtual binding_path lookup_binding(const std::string & /*vname*/) const override { /* i_link: -1 for global environment * j_slot: not used */ diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index 3a3de6ac..91366ca9 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -135,6 +135,9 @@ namespace xo { **/ std::map> layer_var_map_; + /** all lambdas nested once inside this lambda's body **/ + std::map> nested_lambda_map_; + /** established (once) by @ref attach_envs. * * @note data dependency on ancestor expressions that don't exist yet diff --git a/include/xo/expression/binding_path.hpp b/include/xo/expression/binding_path.hpp index a3692ed1..7aef8c51 100644 --- a/include/xo/expression/binding_path.hpp +++ b/include/xo/expression/binding_path.hpp @@ -13,8 +13,8 @@ namespace xo { * providing its location. **/ struct binding_path { - /** @of parent links to traverse. -1 if global **/ - int i_link_ = -1; + /** @of parent links to traverse. -1 if global. -2 if sentinel **/ + int i_link_ = -2; /** for variables bound in some local environment: * slot# within that environment. * diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index fba94694..320e1eda 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -83,7 +83,7 @@ namespace xo { * Motivation is to unify Variables that would use the same * binding_path to resolve their runtime location. */ - std::map> var_map; + std::map> var_map; for (const auto & arg : local_env_->argv()) { /* each arg name can appear at most once @@ -148,6 +148,24 @@ namespace xo { this->free_var_set_ = this->calc_free_variables(); + std::map> nested_lambda_map; + + this->body_->visit_layer + ([&nested_lambda_map] + (ref::brw expr) + { + if (expr->extype() == exprtype::lambda) { + ref::brw lm = Lambda::from(expr); + + nested_lambda_map[lm->name()] = lm.get(); + } + }); + + this->nested_lambda_map_ = std::move(nested_lambda_map); + + /* in particular: + * - establish binding path for each variable + */ this->body_->attach_envs(local_env_); } /*ctor*/ @@ -155,6 +173,7 @@ namespace xo { Lambda::attach_envs(ref::brw p) { local_env_->assign_parent(p); + /** establish a binding path for each variable **/ } void diff --git a/src/expression/Variable.cpp b/src/expression/Variable.cpp index 71d47655..766e0c67 100644 --- a/src/expression/Variable.cpp +++ b/src/expression/Variable.cpp @@ -8,7 +8,11 @@ namespace xo { void Variable::attach_envs(ref::brw e) { /** e makes accessible all enclosing lexical scopes **/ - this->path_ = e->lookup_binding(this->name_); + if (this->path_.i_link_ == -2 /*sentinel*/) { + this->path_ = e->lookup_binding(this->name_); + } else { + /* have already established binding for this Variable */ + } } /*attach_envs*/ void From 8cf89f5eeffb3f2cb52a2863ee964569b3db276a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Jul 2024 16:37:06 -0400 Subject: [PATCH 1222/2524] xo-expression: + LocalEnv::lookup_local_binding() --- include/xo/expression/LocalEnv.hpp | 5 +++++ src/expression/LocalEnv.cpp | 23 ++++++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/include/xo/expression/LocalEnv.hpp b/include/xo/expression/LocalEnv.hpp index 3106dc6c..ec3d4c81 100644 --- a/include/xo/expression/LocalEnv.hpp +++ b/include/xo/expression/LocalEnv.hpp @@ -35,6 +35,11 @@ namespace xo { int n_arg() const { return argv_.size(); } TypeDescr fn_arg(uint32_t i) const { return argv_[i]->valuetype(); } + /** report binding path for a formal parameter. + * Returns sentinel if @p vname doesn't appear in @ref argv_ + **/ + binding_path lookup_local_binding(const std::string & vname) const; + /** single-assign this environment's origin **/ void assign_origin(Lambda * p) { assert(origin_ == nullptr); diff --git a/src/expression/LocalEnv.cpp b/src/expression/LocalEnv.cpp index fb724aeb..8b834278 100644 --- a/src/expression/LocalEnv.cpp +++ b/src/expression/LocalEnv.cpp @@ -8,7 +8,7 @@ namespace xo { namespace ast { binding_path - LocalEnv::lookup_binding(const std::string & vname) const + LocalEnv::lookup_local_binding(const std::string & vname) const { int j_slot = 0; for (const auto & arg : argv_) { @@ -17,12 +17,25 @@ namespace xo { ++j_slot; } - auto tmp = parent_env_->lookup_binding(vname); + return { -2 /*i_link: sentinel*/, 0 }; + } /*lookup_local_binding*/ - if (tmp.i_link_ == -1) - return tmp; + binding_path + LocalEnv::lookup_binding(const std::string & vname) const + { + { + auto local = this->lookup_local_binding(vname); + + if (local.i_link_ == 0) + return local; + } + + auto free = parent_env_->lookup_binding(vname); + + if (free.i_link_ == -1) + return free; else - return { tmp.i_link_ + 1, tmp.j_slot_ }; + return { free.i_link_ + 1, free.j_slot_ }; } /*lookup_binding*/ } /*namespace ast*/ } /*namespace xo*/ From cdb4dd8427d46f5db12e65be4817383b8c18b1a6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 3 Jul 2024 16:37:29 -0400 Subject: [PATCH 1223/2524] xo-expression: + Lambda::captured_var_set; assigned in ctor --- include/xo/expression/Lambda.hpp | 3 ++ src/expression/Lambda.cpp | 50 ++++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index 91366ca9..40083875 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -125,6 +125,9 @@ namespace xo { /** free variables for this lambda **/ std::set free_var_set_; + /** variables that appear free in some nested lambda **/ + std::set captured_var_set_; + /** map giving unique identity to each variable appearing in this layer. * includes: * - formal parameters diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index 320e1eda..cf9112cd 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -149,24 +149,50 @@ namespace xo { this->free_var_set_ = this->calc_free_variables(); std::map> nested_lambda_map; + { + this->body_->visit_layer + ([&nested_lambda_map] + (ref::brw expr) + { + if (expr->extype() == exprtype::lambda) { + ref::brw lm = Lambda::from(expr); - this->body_->visit_layer - ([&nested_lambda_map] - (ref::brw expr) - { - if (expr->extype() == exprtype::lambda) { - ref::brw lm = Lambda::from(expr); - - nested_lambda_map[lm->name()] = lm.get(); - } - }); - + nested_lambda_map[lm->name()] = lm.get(); + } + }); + } this->nested_lambda_map_ = std::move(nested_lambda_map); + /* establish the set of captured local vars. + * These are any formal parameters that appear free in + * any layer of a nested lambda. + */ + std::set captured_var_set; + { + for (const auto & ix : nested_lambda_map_) { + std::set nested_free_var_set + = ix.second->get_free_variables(); + + for (const auto & jx : nested_free_var_set) { + /* check whether variable *jx is one of this lambda's formals */ + auto bind = this->local_env_->lookup_local_binding(jx); + + if (bind.i_link_ == 0) { + /* yup, it's a formal parameter of this lambda */ + captured_var_set.insert(jx); + } + } + } + } + + this->captured_var_set_ = std::move(captured_var_set); + /* in particular: - * - establish binding path for each variable + * - establish binding path (intrusively) for each variable + * assigns Variable::path_ */ this->body_->attach_envs(local_env_); + } /*ctor*/ void From 6f4f06f1b1f69734438d201da9abfb1660b31c37 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Jul 2024 13:52:03 -0400 Subject: [PATCH 1224/2524] xo-pyjit: + reflect fyunction type so can declare vars w/ that type --- src/pyjit/pyjit.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 184fb309..04cfd968 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -17,6 +17,7 @@ namespace xo { using xo::ast::llvmintrinsic; using xo::pyutil::pycaller_base; using xo::pyutil::pycaller; + using xo::reflect::Reflect; using xo::ref::rp; //using xo::ref::Refcount; using xo::ref::unowned_ptr; @@ -54,6 +55,11 @@ namespace xo { { using caller_type = pycaller; + /* we want native function type reflected; + * need this so we can declare function-valued variables + */ + Reflect::require(); + caller_type::declare_once(m, pycaller_id_str); /* factory function takes function pointer of type From a0d748ef9f506597132a9859fb37221d707dbb64 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Jul 2024 13:52:31 -0400 Subject: [PATCH 1225/2524] xo-pyjit: fix: MachPipeline::codegen_toplevel() instead of codegen() --- src/pyjit/pyjit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 04cfd968..527cfe1a 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -156,7 +156,7 @@ namespace xo { py::doc("write to console with state of all jit-owned dynamic libraries")) .def("codegen", [](MachPipeline & jit, const rp & expr) { - return jit.codegen(expr.borrow()); + return jit.codegen_toplevel(expr.borrow()); }, py::arg("x"), py::doc("generate llvm (IR) code for Expression x"), From 921c70dcd74ccc8a7469aab32687df0cee32f91e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Jul 2024 18:51:23 -0400 Subject: [PATCH 1226/2524] xo-jit: basically drop ex_kaleidoscope4, jit .h removed --- example/ex_kaleidoscope4/ex_kaleidoscope4.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp b/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp index 63926aad..46f0c81d 100644 --- a/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp +++ b/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp @@ -1,6 +1,6 @@ /** ex_kaleidoscop4.cpp **/ -#include "xo/jit/KaleidoscopeJit.hpp" +#include "xo/jit/Jit.hpp" #include int @@ -8,9 +8,9 @@ main() { using std::cerr; using std::endl; - auto jit = xo::jit::KaleidoscopeJIT::Create(); + //auto jit = xo::jit::KaleidoscopeJIT::Create(); - cerr << "created kaleidoscope jit successfully" << endl; + //cerr << "created kaleidoscope jit successfully" << endl; } /** end ex_kaleidoscope4.cpp **/ From fdc5d46fd7b340c716ee4a26aa89c96516fc0241 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Jul 2024 20:26:07 -0400 Subject: [PATCH 1227/2524] xo-jit: + runtime_binding_path, ++ to activation_record --- include/xo/jit/activation_record.hpp | 82 ++++++++++++++++++++++++++-- src/jit/MachPipeline.cpp | 4 +- src/jit/activation_record.cpp | 8 ++- 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index c2aba2dd..15ee76f5 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -6,6 +6,7 @@ #pragma once #include "LlvmContext.hpp" +#include "xo/expression/Lambda.hpp" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" # include @@ -16,21 +17,92 @@ namespace xo { namespace jit { - /** scope for a stack frame associated with a user-defined function + /** analagous to xo::ast::binding_path, + * but with locations renumbered to include only vars that belong to an explict runtime + * environment object; in other words we exclude vars with stack-only storage + **/ + struct runtime_binding_path { + public: + runtime_binding_path() = default; + runtime_binding_path(int i_rt_link, + int j_rt_slot) + : i_rt_link_{i_rt_link}, j_rt_slot_{j_rt_slot} {} + + static runtime_binding_path local(int j_rt_slot) { + return runtime_binding_path(0, j_rt_slot); + } + + public: + /** nnumber of parent runtime env links to traverse. -1 if global. -2 if sentinel **/ + int i_rt_link_ = -2; + /** slot# within runtime environment where this variable bound. + * (local vars only -- ignored for global vars) + **/ + int j_rt_slot_ = 0; + }; + + /** + * 1. pattern for a stack frame associated with a user-defined function (some Lambda lm) * - * each function needs its own IR builder, to keep track of things like insert point + * 2. each function needs its own IR builder, to keep track of things like insert point + * + * 3. simple case first. + * if lm->needs_closure_flag() is false, then + * + * a. all formal parameters of lm + * are used only in the layer associated with that lambda's body; in particular + * they aren't free in any nested lambda + * b. conversely, the top layer of lm's body has no free variables. + * The only variables that *do* appear are lm's formal parameters. + * + * In this case, all of lm's formals will be allocated on the stack using regular + * allocInst, and we don't need a closure for lm. + * + * 4. complex case second + * If lm->needs_closure_flag() is true, then either: + * + * a. at least one formal parameter of lm appears free in some nested lambda. + * b. lambda's top layer itself contains one or more free variables. + * + * In either case we will create an explicit environment for lm, + * containing all the variables needed by some nested lambda **/ class activation_record { public: - activation_record() = default; + using Lambda = xo::ast::Lambda; + + public: + activation_record(const ref::rp & lm) + : lambda_{lm} {} llvm::AllocaInst * lookup_var(const std::string & var_name) const; - llvm::AllocaInst * alloc_var(const std::string & var_name, + /** + * @p j_slot index number (0-based) for var_name in formal parameter list for + * its originating lambda + **/ + llvm::AllocaInst * alloc_var(std::size_t j_slot, + const std::string & var_name, llvm::AllocaInst * alloca); private: - /** maps named slots in a stack frame to logical addresses **/ + /** this activation record created on behalf of a call to @ref lambda_. + * @ref Variable::path_ specifies a logical path to a variable, + * but does not distinguish stack-native variables from variables in explicit + * runtime environment records. + * + **/ + ref::rp lambda_; + + /** @c binding_v_[i] specifies how/where to get location for formal parameter number *i* + * of @ref lambda_. + * + **/ + std::vector binding_v_; + + /** maps named slots in a stack frame to logical addresses. + * Only applies to not-captured vars with i_rt_link_=0 + **/ std::map frame_; /* <-> kaleidoscope NamedValues */ }; /*activation_record*/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 9c6bb1cd..041f52b4 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -736,7 +736,7 @@ namespace xo { /** Actual parameters will need their own activation record. * Track its shape here. **/ - this->env_stack_.push(activation_record()); + this->env_stack_.push(activation_record(lambda.get())); { log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); @@ -769,7 +769,7 @@ namespace xo { * in lambda body. * */ - env_stack_.top().alloc_var(arg_name, alloca); + env_stack_.top().alloc_var(i, arg_name, alloca); ++i; } } diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index c7f40362..4b6a77a0 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -25,7 +25,8 @@ namespace xo { } /*lookup_var*/ llvm::AllocaInst * - activation_record::alloc_var(const std::string & x, + activation_record::alloc_var(std::size_t j_slot, + const std::string & x, llvm::AllocaInst * alloca) { if (frame_.find(x) != frame_.end()) { @@ -35,6 +36,11 @@ namespace xo { return nullptr; } + if (j_slot >= binding_v_.size()) + binding_v_.resize(j_slot + 1); + + binding_v_[j_slot] = runtime_binding_path::local(j_slot); + frame_[x] = alloca; return alloca; } /*alloc_var*/ From d7192c1d97405a46e93157d35a5fe0c9d0fb3a99 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 7 Jul 2024 13:27:12 -0400 Subject: [PATCH 1228/2524] xo-jit: + explicit env for captured function args [wip, not tested] --- include/xo/jit/MachPipeline.hpp | 2 + include/xo/jit/activation_record.hpp | 135 ++++++++-- include/xo/jit/type2llvm.hpp | 181 +++++++++++++ src/jit/CMakeLists.txt | 1 + src/jit/MachPipeline.cpp | 217 ++-------------- src/jit/activation_record.cpp | 372 ++++++++++++++++++++++++++- src/jit/type2llvm.cpp | 305 ++++++++++++++++++++++ 7 files changed, 992 insertions(+), 221 deletions(-) create mode 100644 include/xo/jit/type2llvm.hpp create mode 100644 src/jit/type2llvm.cpp diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 11589e8c..88c9f544 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -161,6 +161,7 @@ namespace xo { llvm::AllocaInst * create_entry_frame_alloca(llvm::Function * llvm_fn, llvm::StructType * frame_llvm_type); +#ifdef OBSOLETE // see activation_record::create_entry_block_alloca() /** codegen helper for a user-defined function (codegen_lambda()): * create stack slot on behalf of some formal parameter to a function, * so we can avoid SSA restriction on function body @@ -170,6 +171,7 @@ namespace xo { llvm::AllocaInst * create_entry_block_alloca(llvm::Function * llvm_fn, const std::string & var_name, TypeDescr var_type); +#endif private: /** (re)create pipeline to turn expressions into llvm IR code **/ diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index 15ee76f5..82763e6c 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -28,31 +28,61 @@ namespace xo { int j_rt_slot) : i_rt_link_{i_rt_link}, j_rt_slot_{j_rt_slot} {} + static runtime_binding_path stackonly() { + return runtime_binding_path(0, -1); + } static runtime_binding_path local(int j_rt_slot) { return runtime_binding_path(0, j_rt_slot); } + bool is_stackonly() const { return (i_rt_link_ == 0) && (j_rt_slot_ == -1); } + bool is_captured() const { return !is_stackonly(); } + public: /** nnumber of parent runtime env links to traverse. -1 if global. -2 if sentinel **/ int i_rt_link_ = -2; - /** slot# within runtime environment where this variable bound. + /** >= 0: slot# within explicit runtime environment where this variable bound. * (local vars only -- ignored for global vars) + * -1: stack-only parameter **/ int j_rt_slot_ = 0; }; + struct runtime_binding_detail { + /** Formal index position for this formal parameter. + * Index into @ref activation_record::binding_v_, + * also for @ref Lambda::fn_arg + **/ + int i_argno_ = -1; + + /** instructions for establishing stack address of this variable + * In practice will be either an AllocaInst (for non-captured variables), + * or result of IRBuilder<>::CreateInBoundsGEP (for captured variables). + **/ + llvm::Value * llvm_addr_ = nullptr; + + /** llvm type associated with stack-allocated variable. + * Determines (when combined with llvm::DataLayout) how much space + * will be required for this particular variable + **/ + llvm::Type * llvm_type_ = nullptr; + }; + /** * 1. pattern for a stack frame associated with a user-defined function (some Lambda lm) * * 2. each function needs its own IR builder, to keep track of things like insert point * * 3. simple case first. - * if lm->needs_closure_flag() is false, then + * if lm->needs_closure_flag() is false, then: * - * a. all formal parameters of lm + * a. still need a closure-shaped object, because when we invoke function, we may + * not know until runtime whether it relies on closure. + * For such function we will generate a closure with empty environment pointer. + * b. all formal parameters of lm * are used only in the layer associated with that lambda's body; in particular * they aren't free in any nested lambda - * b. conversely, the top layer of lm's body has no free variables. + * c. conversely, the top layer of lm's body has no free variables. * The only variables that *do* appear are lm's formal parameters. * * In this case, all of lm's formals will be allocated on the stack using regular @@ -70,20 +100,76 @@ namespace xo { class activation_record { public: using Lambda = xo::ast::Lambda; + using TypeDescr = xo::reflect::TypeDescr; public: - activation_record(const ref::rp & lm) - : lambda_{lm} {} + activation_record(const ref::rp & lm); - llvm::AllocaInst * lookup_var(const std::string & var_name) const; + const ref::rp lambda() const { return lambda_; } - /** - * @p j_slot index number (0-based) for var_name in formal parameter list for - * its originating lambda + /** retrieve @c llvm::Value* representing the primary stack location + * for formal parameter @p var_name **/ - llvm::AllocaInst * alloc_var(std::size_t j_slot, - const std::string & var_name, - llvm::AllocaInst * alloca); + const runtime_binding_detail * lookup_var(const std::string & var_name) const; + + /** Remember allocation of a function variable on the stack + * + * @param var_name. formal parameter name + * @param binding. address + supporting details for + * primary (stack-allocated) storage for this variable + **/ + const runtime_binding_detail * alloc_var(const std::string & var_name, + const runtime_binding_detail & binding); + +#ifdef NOT_USING + llvm::AllocaInst * create_runtime_localenv_alloca(ref::brw llvm_cx, + //const llvm::DataLayout & data_layout, + llvm::Function * llvm_fn, + llvm::IRBuilder<> & fn_ir_builder); +#endif + + runtime_binding_detail create_entry_block_alloca(ref::brw llvm_cx, + //const llvm::DataLayout & data_layout, + llvm::Function * llvm_fn, + llvm::IRBuilder<> & fn_ir_builder, + int i_arg, + const std::string & var_name, + TypeDescr var_td); + + /** generate instructions that establish stacck location for a local-environment slot + * + * @param llvm_cx. handle for context -- manages storage for llvm::Types + related + * @param localenv_llvm_type. describes contents of local environment + * for a particular function. Same as @c localenv_alloca->getAllocatedType() + * @param localenv_alloca. stack location for local environment + * @param i_slot. 0-based slot number within local environment, + * for which address is required + * @param fn_ir_builder. insertion point for generated instructions + * that compute target slot address (will be at/near top of function, + * since we will copy captured function arguments to localenv, + * then use the localenv copy exclusively. + * @return value representing localenv slot address + **/ + llvm::Value * runtime_localenv_slot_addr(ref::brw llvm_cx, + llvm::StructType * localenv_llvm_type, + llvm::AllocaInst * localenv_alloca, + int i_slot, + llvm::IRBuilder<> & fn_ir_builder); + + /** establish storage for formal parameters on behalf of a new-but-empty + * llvm function @p llvm_fn. Creates llvm IR instructions on function + * entry that + * 1. allocates stack space for function parameters. + * 2. stores incoming parameters in that stack space. + * + * Strategy: + * - for stackonly parameters, use individual @c llvm::AllocaInst instances + * - create custom @c llvm::StructType for captured parameters, also initially stack-allocated + **/ + bool bind_locals(ref::brw llvm_cx, + //const llvm::DataLayout & data_layout, + llvm::Function * llvm_fn, + llvm::IRBuilder<> & ir_builder); private: /** this activation record created on behalf of a call to @ref lambda_. @@ -94,16 +180,27 @@ namespace xo { **/ ref::rp lambda_; - /** @c binding_v_[i] specifies how/where to get location for formal parameter number *i* - * of @ref lambda_. - * + /** @c binding_v_[i] specifies how/where we mean to navigate to + * location for formal parameter number *i* of @ref lambda_. **/ std::vector binding_v_; - /** maps named slots in a stack frame to logical addresses. - * Only applies to not-captured vars with i_rt_link_=0 + /** if this function requires an explicit environment, + * gives stack location for that environment. **/ - std::map frame_; /* <-> kaleidoscope NamedValues */ + llvm::AllocaInst * localenv_alloca_ = nullptr; + + /** maps named slots in a stack frame to logical addresses. + * + * - For captured arguments: will refer to slot within stack-allocated local environment + * (an llvm::StructType, created by type2llvm::create_localenv_llvm_type()) + * + * - For non-captured arguments: will refer to stack-allocated argument copy + * + * In either case using copy-to-stack to evade directly confronting + * so we don't have to comply with llvm IR's SSA requirement. + **/ + std::map frame_; /* <-> kaleidoscope NamedValues */ }; /*activation_record*/ } /*namespace jit*/ diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp new file mode 100644 index 00000000..2d532893 --- /dev/null +++ b/include/xo/jit/type2llvm.hpp @@ -0,0 +1,181 @@ +/** @file type2llvm.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "LlvmContext.hpp" +#include "xo/expression/Lambda.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include +//#include + +namespace xo { + namespace jit { + /** + **/ + struct type2llvm { + public: + using Lambda = xo::ast::Lambda; + using TypeDescr = xo::reflect::TypeDescr; + + public: + /** establish suitable llvm representation for a c++ type (described by @p td) + * llvm types are unique'd, at least within @p llvm_cx + **/ + static llvm::Type * td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr td); + + /** establish llvm representation for a function type + * described by @p fn_td + **/ + static llvm::FunctionType * function_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr fn_td); + + /** establish llvm concrete representation for a particular lambda's + * runtime local environment: + * + * ^ + * | parent + * +-------+ | + * parent_env [0] | o-------/ + * +-------+ + * unwind_fn [1] | o-------> env * (*)(env*, ctl) + * +-------+ + * arg[i] [2+i] . ... . + * . ... . + * +-------+ + * + * ctl=0 unwind. finalization for any arg[i] that requires it. + * returns nullptr + * ctl=1 copy. copy runtime environment to heap destination + * and return address of the copy + * + * arg[] comprises the subset of lambda arg names arg[j] for which + * lambda->is_captured(arg[j]) is true + **/ + static llvm::StructType * + create_localenv_llvm_type(xo::ref::brw llvm_cx, + xo::ref::brw lambda); + + /** establish llvm rep'n for a pointer to an abstract local environment: + * + * +-------+ + * | o-------------\ + * +-------+ | + * | + * | + * | + * v + * +-------+ + * parent_env [0] | o-------> _env_api* + * +-------+ + * unwind_fn [1] | o-------> env * (*)(env*, ctl) + * +-------+ + **/ + static llvm::PointerType * + env_api_llvm_ptr_type(xo::ref::brw llvm_cx); + + /** function type: + * @code + * env_api_* (env_api* env, int ctl); + * @endcode + * + * ctl=0 unwind. finalization for any arg[i] that requires it. + * returns nullptr + * ctl=1 copy. copy runtime environment to heap destination + * and return address of the copy + * + * returns function-pointer type + **/ + static llvm::PointerType * + require_localenv_unwind_llvm_fnptr_type(xo::ref::brw llvm_cx, + llvm::PointerType * hint_envptr_llvm_type = nullptr); + + private: + /** establish llvm representation for a function-pointer type + * described by @p fn_td + **/ + static llvm::PointerType * function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td); + + /** establish llvm representation for a struct type described by @p struct_td + **/ + static llvm::StructType * struct_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr struct_td); + + /** establish llvm representation for a pointer type described by @p pointer_td **/ + static llvm::PointerType * pointer_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr pointer_td); + + /** establish llvm abstract representation for a local environment: + * + * ^ + * | parent + * +-------+ | + * parent_env [0] | o-------/ + * +-------+ + * unwind_fn [1] | o-------> env * (*)(env*, ctl) + * +-------+ + * + * ctl=0 unwind. finalization for any arg[i] that requires it. + * returns nullptr + * ctl=1 copy. copy runtime environment to heap destination + * and return address of the copy + * + * Concrete implementation will probably occupy additional memory, + * to store captured lambda variables. + * + * @see type2llvm::function_td_to_llvm_closure_type + **/ + static llvm::StructType * + env_api_llvm_type(xo::ref::brw llvm_cx); + + /** establish llvm abstract representation for a closure: + * struct with + * - [0] function pointer + * - [1] runtime localenv pointer + * + * +-------+ + * | o---------> native function + * +-------+ + * | o---------> runtime localenv + * +-------+ (possibly nullptr) + * + * 1. for primitives, localenv will be null pointer + * 2. for lambdas L with L->requires_closure_flag() = false, + * localenv will also be null pointer + * 3. for lambdas with L->requires_closure_flag() = true, + * + * localenv will (for lambdas requiring closures) + * in practice be struct: + * + * ^ + * | parent + * +-------+ | + * parent_env [0] | o-------/ + * +-------+ + * unwind_fn [1] | o-------> env * (*)(env*, ctl) + * +-------+ + * arg[i] [2+i] . ... . + * . ... . + * +-------+ + * + * ctl=0 unwind. finalization for any arg[i] that requires it. + * returns nullptr + * ctl=1 copy. copy runtime environment to heap destination + * and return address of the copy + * + * Implementation here will just use generic pointer for runtime + * localenv. + **/ + static llvm::StructType * + function_td_to_llvm_closure_type(xo::ref::brw llvm_cx, + TypeDescr fn_td); + + }; /*type2llvm*/ + } /*namespace jit*/ +} /*namespace xo*/ + +/** end type2llvm.hpp **/ diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index 4168eebe..c52f7116 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -7,6 +7,7 @@ set(SELF_SRCS MachPipeline.cpp intrinsics.cpp activation_record.cpp + type2llvm.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 041f52b4..3bd895f0 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -1,6 +1,8 @@ /* @file MachPipeline.cpp */ #include "MachPipeline.hpp" +#include "activation_record.hpp" +#include "type2llvm.hpp" #include namespace xo { @@ -156,187 +158,9 @@ namespace xo { return nullptr; } /*codegen_constant*/ - namespace { - /** REMINDER: - * 1. creation of llvm types is idempotent - * (duplicate calls will receive the same llvm::Type* pointer) - * 2. llvm::Types are never deleted. - **/ - - llvm::Type * - td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td); - - /** obtain llvm representation for a function type with the same signature as - * that represented by @p fn_td - **/ - llvm::FunctionType * - function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) - { - int n_fn_arg = fn_td->n_fn_arg(); - - std::vector llvm_argtype_v; - llvm_argtype_v.reserve(n_fn_arg); - - /** check function args are all known **/ - for (int i = 0; i < n_fn_arg; ++i) { - TypeDescr arg_td = fn_td->fn_arg(i); - - llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx, arg_td); - - if (!llvm_argtype) - return nullptr; - - llvm_argtype_v.push_back(llvm_argtype); - } - - TypeDescr retval_td = fn_td->fn_retval(); - llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx, retval_td); - - if (!llvm_retval) - return nullptr; - - auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, - llvm_argtype_v, - false /*!varargs*/); - return llvm_fn_type; - } - - llvm::PointerType * - function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) - { - auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); - - /** like C: llvm IR doesn't support function-valued variables; - * it does however support pointer-to-function-valued variables - **/ - auto * llvm_ptr_type - = llvm::PointerType::get(llvm_fn_type, - 0 /*numbered address space*/); - - return llvm_ptr_type; - } - - /** - * Generate llvm::Type correspoinding to a TypeDescr for a struct. - **/ - llvm::StructType * - struct_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr struct_td) - { - // see - // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] - - auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); - - /* note: object pointer ignored for struct types, - * since number of members is known at compile time - */ - int n_member = struct_td->n_child(nullptr /*&object*/); - - /* one type for each struct member */ - std::vector llvm_membertype_v; - llvm_membertype_v.reserve(n_member); - - for (int i = 0; i < n_member; ++i) { - StructMember const & sm = struct_td->struct_member(i); - - llvm_membertype_v.push_back(td_to_llvm_type(llvm_cx, - sm.get_member_td())); - } - - std::string struct_name = std::string(struct_td->short_name()); - - /* structs with names: within an llvmcontext, must be unique - * - * We can however compare the offsets recorded in xo::reflect with - * offsets chosen by llvm, *once we've created the llvm type* - * - * Also, we can't guarantee that a c++ type was completely reflected -- - * it's possible one or more members were omitted, in which case - * it's unlikely at best that llvm chooses the same layout. - * - * Instead: tell llvm to make packed struct, - * and introduce dummy members for padding. - * - * A consequence is we have to maintain mapping between llvm's - * member numbering and xo::reflect's - */ - llvm::StructType * llvm_struct_type - = llvm::StructType::create(llvm_cx_ref, - llvm_membertype_v, - llvm::StringRef(struct_name), - false /*!isPacked*/); - - /* TODO: inspect (how) offsets that llvm is using - * we need them to match what C++ chose - * - * (because we want jitted llvm code to interoperate with - * C++ library code that has structs) - */ - - // GetElementPtrInst is interesting, - // but I think that's for generating code - - return llvm_struct_type; - } /*struct_td_to_llvm_type*/ - - llvm::PointerType * - pointer_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr pointer_td) - { - assert(pointer_td->is_pointer()); - - TypeDescr dest_td = pointer_td->fixed_child_td(0); - - llvm::Type * llvm_dest_type = td_to_llvm_type(llvm_cx, dest_td); - - llvm::PointerType * llvm_ptr_type - = llvm::PointerType::getUnqual(llvm_dest_type); - - return llvm_ptr_type; - } /*pointer_td_llvm_type*/ - - llvm::Type * - td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { - auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); - - if (td->is_function()) { - /* in this context, we're looking for a representation for a value, - * i.e. something that can be stored in a variable - */ - return function_td_to_llvm_fnptr_type(llvm_cx, td); - } else if (td->is_struct()) { - return struct_td_to_llvm_type(llvm_cx, td); - } else if (td->is_pointer()) { - return pointer_td_to_llvm_type(llvm_cx, td); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt1Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt8Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt16Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt32Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt64Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getFloatTy(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getDoubleTy(llvm_cx_ref); - } else { - cerr << "td_to_llvm_type: no llvm type available for T" - << xtag("T", td->short_name()) - << endl; - return nullptr; - } - } - } - llvm::Type * MachPipeline::codegen_type(TypeDescr td) { - return td_to_llvm_type(llvm_cx_.borrow(), td); + return type2llvm::td_to_llvm_type(llvm_cx_.borrow(), td); } llvm::Function * @@ -368,7 +192,7 @@ namespace xo { TypeDescr fn_td = expr->valuetype(); llvm::FunctionType * llvm_fn_type - = function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); if (!llvm_fn_type) return nullptr; @@ -577,7 +401,7 @@ namespace xo { */ llvm::FunctionType * llvm_fn_type - = function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); + = type2llvm::function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); return ir_builder.CreateCall(llvm_fn_type, llvm_fnval, @@ -586,6 +410,7 @@ namespace xo { } /*codegen_apply*/ +#ifdef OBSOLETE /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ llvm::AllocaInst * MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, @@ -603,8 +428,8 @@ namespace xo { llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), llvm_fn->getEntryBlock().begin()); - llvm::Type * llvm_var_type = td_to_llvm_type(llvm_cx_.borrow(), - var_type); + llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx_.borrow(), + var_type); log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); if (log) { @@ -627,6 +452,7 @@ namespace xo { return retval; } /*create_entry_block_alloca*/ +#endif std::vector> @@ -678,8 +504,8 @@ namespace xo { #endif llvm::FunctionType * llvm_fn_type - = function_td_to_llvm_type(llvm_cx_.borrow(), - lambda->valuetype()); + = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), + lambda->valuetype()); /* create (initially empty) function */ fn = llvm::Function::Create(llvm_fn_type, @@ -734,10 +560,18 @@ namespace xo { ir_builder.SetInsertPoint(block); /** Actual parameters will need their own activation record. - * Track its shape here. + * Track its shape + setup/teardown here. **/ this->env_stack_.push(activation_record(lambda.get())); + bool ok_flag = this->env_stack_.top().bind_locals(llvm_cx_, llvm_fn, ir_builder); + + if (!ok_flag) { + this->env_stack_.pop(); + return nullptr; + } + +#ifdef OBSOLETE { log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); @@ -773,6 +607,7 @@ namespace xo { ++i; } } +#endif llvm::Value * retval = this->codegen(lambda->body(), ir_builder); @@ -827,14 +662,16 @@ namespace xo { return nullptr; } - llvm::AllocaInst * alloca = env_stack_.top().lookup_var(var->name()); + activation_record & ar = env_stack_.top(); - if (!alloca) + const runtime_binding_detail * binding = ar.lookup_var(var->name()); + + if (!binding) return nullptr; /* code to load value from stack */ - return ir_builder.CreateLoad(alloca->getAllocatedType(), - alloca, + return ir_builder.CreateLoad(binding->llvm_type_, + binding->llvm_addr_, var->name().c_str()); } /*codegen_variable*/ diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index 4b6a77a0..413412af 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -1,6 +1,7 @@ /* @file activation_record.cpp */ #include "activation_record.hpp" +#include "type2llvm.hpp" #include "xo/indentlog/print/tag.hpp" #include @@ -9,7 +10,29 @@ namespace xo { using std::cerr; using std::endl; - llvm::AllocaInst * + activation_record::activation_record(const ref::rp & lm) + : lambda_{lm}, + binding_v_(lm->n_arg()) + { + /* populate binding_v_ */ + int n_arg = lm->n_arg(); + binding_v_.resize(n_arg); + + /* next slot# to use in explicit activation record */ + int rt_env_slot = 0; + + for (int i_arg = 0; i_arg < n_arg; ++i_arg) { + if (lm->is_captured(lm->i_argname(i_arg))) { + /* local param #i_arg needs a slot in explicit activation record */ + binding_v_[i_arg] = runtime_binding_path::local(rt_env_slot); + ++rt_env_slot; + } else { + binding_v_[i_arg] = runtime_binding_path::stackonly(); + } + } + } /*ctor*/ + + const runtime_binding_detail * activation_record::lookup_var(const std::string & x) const { auto ix = frame_.find(x); @@ -21,13 +44,12 @@ namespace xo { return nullptr; } - return ix->second; + return &(ix->second); } /*lookup_var*/ - llvm::AllocaInst * - activation_record::alloc_var(std::size_t j_slot, - const std::string & x, - llvm::AllocaInst * alloca) + const runtime_binding_detail * + activation_record::alloc_var(const std::string & x, + const runtime_binding_detail & binding) { if (frame_.find(x) != frame_.end()) { cerr << "activation_record::alloc_var: variable x already present in frame" @@ -36,14 +58,340 @@ namespace xo { return nullptr; } - if (j_slot >= binding_v_.size()) - binding_v_.resize(j_slot + 1); + frame_[x] = binding; - binding_v_[j_slot] = runtime_binding_path::local(j_slot); - - frame_[x] = alloca; - return alloca; + return &(frame_[x]); } /*alloc_var*/ + + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + runtime_binding_detail + activation_record::create_entry_block_alloca(ref::brw llvm_cx, + //const llvm::DataLayout & data_layout, + llvm::Function * llvm_fn, + llvm::IRBuilder<> & fn_ir_builder, + int i_arg, + const std::string & var_name, + TypeDescr var_type) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn), + xtag("var_name", var_name), + xtag("var_type", var_type->short_name())); + + llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, + var_type); + + log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); + if (log) { + std::string llvm_var_type_str; + llvm::raw_string_ostream ss(llvm_var_type_str); + llvm_var_type->print(ss); + + log(xtag("llvm_var_type", llvm_var_type_str)); + } + + if (!llvm_var_type) + return runtime_binding_detail{}; /*sentinel*/ + + llvm::AllocaInst * stackaddr = fn_ir_builder.CreateAlloca(llvm_var_type, + nullptr, + var_name); + + log && log(xtag("alloca", (void*)stackaddr), + xtag("align", stackaddr->getAlign().value()) + //xtag("size", retval->getAllocationSize(data_layout).value()) + ); + + return {i_arg, stackaddr, llvm_var_type}; + } /*create_entry_block_alloca*/ + +#ifdef NOT_USING + llvm::AllocaInst * + activation_record::create_runtime_localenv_alloca(ref::brw llvm_cx, + //const llvm::DataLayout & data_layout, + llvm::Function * llvm_fn, + llvm::IRBuilder<> & ir_builder) + + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn)); + + llvm::StructType * localenv_llvm_type + = type2llvm::create_localenv_llvm_type(llvm_cx, lambda_.borrow()); + + if (!localenv_llvm_type) + return nullptr; + + llvm::AllocaInst * retval = ir_builder.CreateAlloca(localenv_llvm_type, + nullptr /*ArraySize*/, + "_localenv"); + + log && log(xtag("alloca", (void*)retval), + xtag("align", retval->getAlign().value()) + //xtag("size", retval->getAllocationSize(data_layout).value()) + ); + + return retval; + } /*create_runtime_localenv_alloca*/ +#endif + + llvm::Value * + activation_record::runtime_localenv_slot_addr(ref::brw llvm_cx, + llvm::StructType * localenv_llvm_type, + llvm::AllocaInst * localenv_alloca, + int i_slot, +#ifdef NOT_HERE + llvm::Value * llvm_slot_value, +#endif + llvm::IRBuilder<> & tmp_ir_builder) + { + llvm::Value * i32_slot + = llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, + i_slot /*value*/)); + std::array index_v = { + {i32_slot /*environment slot #0*/}}; + + llvm::Value * llvm_localenv_slot_ptr + = tmp_ir_builder.CreateInBoundsGEP(localenv_llvm_type, + localenv_alloca, + index_v); + + return llvm_localenv_slot_ptr; + +#ifdef NOT_HERE + tmp_ir_builder.CreateStore(llvm_value, //llvm_0ptr, + llvm_parent_env_ptr); +#endif + } /*runtime_localenv_slot_addr*/ + + bool + activation_record::bind_locals(ref::brw llvm_cx, + //const llvm::DataLayout & data_layout, + llvm::Function * llvm_fn, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda_->name())); + + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), + llvm_fn->getEntryBlock().begin()); + + /* 1st pass: handle stackonly variables + * + * We presume this must come first, + * for subsequent mem2reg optimization pass to consider + */ + { + int i_arg = 0; + for (auto & arg : llvm_fn->args()) { + std::string arg_name = std::string(arg.getName()); + + log && log("nested environment", + xtag("i", i_arg), + xtag("arg[i]", arg_name), + xtag("stackonly(i)", binding_v_[i_arg].is_stackonly())); + + if (binding_v_[i_arg].is_stackonly()) { + /* stack location for arg[i] */ + runtime_binding_detail binding + = create_entry_block_alloca(llvm_cx, + //data_layout, + llvm_fn, + tmp_ir_builder, + i_arg, + arg_name, + lambda_->fn_arg(i_arg)); + + if (!binding.llvm_addr_) + return false; + + /* store on function entry + * see codegen_variable() for corresponding load + */ + ir_builder.CreateStore(&arg, binding.llvm_addr_); + + /* remember stack location for reference + assignment + * in lambda body. + * + */ + this->alloc_var(arg_name, binding); + } + + ++i_arg; + } + } + + /* REMINDER: all functions need to follow the closure pattern, + * to accomodate cases where we don't know until runtime + * what kind of function we are invoking. + * + * This means: + * - always represent function in IR by a closure-shaped object + * + * +-------+ + * | o---------> native function + * +-------+ + * | o---------> runtime localenv + * +-------+ (possibly nullptr) + * + * We hope to optimize away the closures in cases where we know + * their contents at compile time + * + */ + + /* 2nd pass: handle captured formal parameters */ + if (lambda_->needs_closure_flag()) { + llvm::StructType * localenv_llvm_type + = type2llvm::create_localenv_llvm_type(llvm_cx, lambda_.borrow()); +#ifdef NOT_USING + llvm::PointerType * envapiptr_llvm_type + = type2llvm::env_api_llvm_ptr_type(llvm_cx); +#endif + + if (!localenv_llvm_type) + return false; + + /* + * runtime localenv: ^ + * | parent + * +-------+ | + * parent_env [0] | o-------/ + * +-------+ + * unwind_fn [1] | o-------> env * (*)(env*, ctl) + * +-------+ + * arg[i] [2+i] . ... . + * . ... . + * +-------+ + * + * ctl=0 unwind. finalization for any arg[i] that requires it. + * returns nullptr + * ctl=1 copy. copy runtime environment to heap destination + * and return address of the copy + * + * arg[] comprises the subset of lambda arg names arg[j] for which + * lambda->is_captured(arg[j]) is true + */ + llvm::AllocaInst * localenv_alloca + = tmp_ir_builder.CreateAlloca(localenv_llvm_type, + nullptr /*ArraySize*/, + "_localenv"); + + if (!localenv_alloca) + return false; + + /* remember environemnt location. + * Will need this if need to copy-to-stack + */ + this->localenv_alloca_ = localenv_alloca; + + int i_localenv_slot = 0; + + /* store localenv->parent_env */ + { + llvm::Value * slot_addr + = runtime_localenv_slot_addr(llvm_cx, + localenv_llvm_type, + localenv_alloca, + i_localenv_slot, + //llvm_0ptr, + tmp_ir_builder); + + if (!slot_addr) + return false; + + ++i_localenv_slot; + + /* null pointer for now */ + /* TODO: get parent environment (from runtime closure created for this function) */ + llvm::Value * llvm_0ptr + = llvm::ConstantPointerNull::get(type2llvm::env_api_llvm_ptr_type(llvm_cx)); + + tmp_ir_builder.CreateStore(llvm_0ptr, + slot_addr); + } + + /* store localenv->unwind_fn */ + { + llvm::Value * slot_addr + = runtime_localenv_slot_addr(llvm_cx, + localenv_llvm_type, + localenv_alloca, + i_localenv_slot, + //llvm_0ptr, + tmp_ir_builder); + + if (!slot_addr) + return false; + + ++i_localenv_slot; + + /* null function pointer for now */ + /* TODO: construct unwind function */ + llvm::Value * llvm_0ptr + = (llvm::ConstantPointerNull::get + (type2llvm::require_localenv_unwind_llvm_fnptr_type(llvm_cx))); + + tmp_ir_builder.CreateStore(llvm_0ptr, + slot_addr); + } + + int i_arg = 0; + + for (llvm::Argument & arg : llvm_fn->args()) { + std::string arg_name = std::string(arg.getName()); + + log && log("nested environment", + xtag("i", i_arg), + xtag("arg[i]", arg_name), + xtag("captured(i)", binding_v_[i_arg].is_captured())); + + if (binding_v_[i_arg].is_captured()) { + // do something with runtime-local-env for this llvm_fn + + /* remember stack location for reference + assignment + * in lambda body. + * + */ + + TypeDescr arg_td = lambda_->fn_arg(i_arg); + + llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, arg_td); + + llvm::Value * slot_addr + = runtime_localenv_slot_addr(llvm_cx, + localenv_llvm_type, + localenv_alloca, + i_localenv_slot, + tmp_ir_builder); + + if (!slot_addr) + return false; + + ++i_localenv_slot; + + tmp_ir_builder.CreateStore(&arg, slot_addr); + + runtime_binding_detail binding = { i_arg, slot_addr, llvm_var_type }; + + this->alloc_var(arg_name, binding); + } + + ++i_arg; + } + } + + return true; + } /*bind_locals*/ } /*namespace jit*/ } /*namespace xo*/ diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp new file mode 100644 index 00000000..23ee48c4 --- /dev/null +++ b/src/jit/type2llvm.cpp @@ -0,0 +1,305 @@ +/* @file type2llvm.cpp */ + +#include "type2llvm.hpp" +#include "xo/reflect/Reflect.hpp" +//#include "xo/reflect/struct/StructMember.hpp" + +namespace xo { + using xo::reflect::Reflect; + using xo::reflect::TypeDescr; + using xo::reflect::StructMember; + using std::cerr; + using std::endl; + + namespace jit { + /** REMINDER: + * 1. creation of llvm types is idempotent + * (duplicate calls will receive the same llvm::Type* pointer) + * 2. llvm::Types are never deleted. + **/ + + llvm::Type * + type2llvm::td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + if (td->is_function()) { + /* in this context, we're looking for a representation for a value, + * i.e. something that can be stored in a variable + */ + return function_td_to_llvm_fnptr_type(llvm_cx, td); + } else if (td->is_struct()) { + return struct_td_to_llvm_type(llvm_cx, td); + } else if (td->is_pointer()) { + return pointer_td_to_llvm_type(llvm_cx, td); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt1Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt8Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt16Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt32Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt64Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getFloatTy(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getDoubleTy(llvm_cx_ref); + } else { + cerr << "td_to_llvm_type: no llvm type available for T" + << xtag("T", td->short_name()) + << endl; + return nullptr; + } + } /*td_to_llvm_type*/ + + /** obtain llvm representation for a function type with the same signature as + * that represented by @p fn_td + **/ + llvm::FunctionType * + type2llvm::function_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + int n_fn_arg = fn_td->n_fn_arg(); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(n_fn_arg); + + /** check function args are all known **/ + for (int i = 0; i < n_fn_arg; ++i) { + TypeDescr arg_td = fn_td->fn_arg(i); + + llvm::Type * llvm_argtype = type2llvm::td_to_llvm_type(llvm_cx, arg_td); + + if (!llvm_argtype) + return nullptr; + + llvm_argtype_v.push_back(llvm_argtype); + } + + TypeDescr retval_td = fn_td->fn_retval(); + llvm::Type * llvm_retval = type2llvm::td_to_llvm_type(llvm_cx, retval_td); + + if (!llvm_retval) + return nullptr; + + auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, + llvm_argtype_v, + false /*!varargs*/); + return llvm_fn_type; + } /*function_td_to_llvm_type*/ + + llvm::PointerType * + type2llvm::function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); + + /** like C: llvm IR doesn't support function-valued variables; + * it does however support pointer-to-function-valued variables + **/ + auto * llvm_ptr_type + = llvm::PointerType::get(llvm_fn_type, + 0 /*numbered address space*/); + + return llvm_ptr_type; + } + + /** + * Generate llvm::Type correspoinding to a TypeDescr for a struct. + **/ + llvm::StructType * + type2llvm::struct_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr struct_td) + { + // see + // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] + + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + /* note: object pointer ignored for struct types, + * since number of members is known at compile time + */ + int n_member = struct_td->n_child(nullptr /*&object*/); + + /* one type for each struct member */ + std::vector llvm_membertype_v; + llvm_membertype_v.reserve(n_member); + + for (int i = 0; i < n_member; ++i) { + StructMember const & sm = struct_td->struct_member(i); + + llvm_membertype_v.push_back(type2llvm::td_to_llvm_type(llvm_cx, + sm.get_member_td())); + } + + std::string struct_name = std::string(struct_td->short_name()); + + /* structs with names: within an llvmcontext, must be unique + * + * We can however compare the offsets recorded in xo::reflect with + * offsets chosen by llvm, *once we've created the llvm type* + * + * Also, we can't guarantee that a c++ type was completely reflected -- + * it's possible one or more members were omitted, in which case + * it's unlikely at best that llvm chooses the same layout. + * + * Instead: tell llvm to make packed struct, + * and introduce dummy members for padding. + * + * A consequence is we have to maintain mapping between llvm's + * member numbering and xo::reflect's + */ + llvm::StructType * llvm_struct_type + = llvm::StructType::create(llvm_cx_ref, + llvm_membertype_v, + llvm::StringRef(struct_name), + false /*!isPacked*/); + + /* TODO: inspect (how) offsets that llvm is using + * we need them to match what C++ chose + * + * (because we want jitted llvm code to interoperate with + * C++ library code that has structs) + */ + + // GetElementPtrInst is interesting, + // but I think that's for generating code + + return llvm_struct_type; + } /*struct_td_to_llvm_type*/ + + llvm::PointerType * + type2llvm::pointer_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr pointer_td) + { + assert(pointer_td->is_pointer()); + + TypeDescr dest_td = pointer_td->fixed_child_td(0); + + llvm::Type * llvm_dest_type = type2llvm::td_to_llvm_type(llvm_cx, dest_td); + + llvm::PointerType * llvm_ptr_type + = llvm::PointerType::getUnqual(llvm_dest_type); + + return llvm_ptr_type; + } /*pointer_td_llvm_type*/ + + llvm::PointerType * + type2llvm::require_localenv_unwind_llvm_fnptr_type(xo::ref::brw llvm_cx, + llvm::PointerType * envptr_llvm_type) + { + if (!envptr_llvm_type) + envptr_llvm_type = env_api_llvm_ptr_type(llvm_cx); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(2); + + /* 1st arg is _env_api pointer */ + llvm_argtype_v.push_back(envptr_llvm_type); + + /* 2nd arg is i32 */ + llvm_argtype_v.push_back(llvm::Type::getInt32Ty(llvm_cx->llvm_cx_ref())); + + /* return value is _env_api pointer */ + llvm::Type * retval_llvm_type = envptr_llvm_type; + + /* _env_api* (_env_api*, i32) */ + auto * unwind_llvm_type + = llvm::FunctionType::get(retval_llvm_type, + llvm_argtype_v, + false /*!varargs*/); + + /* _env_api* (*)(_env_api*, i32) */ + auto * unwind_llvm_fnptr_type + = llvm::PointerType::getUnqual(unwind_llvm_type); + + return unwind_llvm_fnptr_type; + } /*require_localenv_unwind_llvm_fnptr_type*/ + + llvm::StructType * + type2llvm::env_api_llvm_type(xo::ref::brw llvm_cx) + { + /* _env_api: base type for a local environment */ + llvm::StructType * env_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref(), + "_env_api"); + + /* _env_api[0]: pointer to a local environment */ + llvm::PointerType * envptr_llvm_type + = llvm::PointerType::getUnqual(env_llvm_type); + + /* _env_api[1]: unwwind/copy function */ + llvm::PointerType * unwind_llvm_fnptr_type + = type2llvm::require_localenv_unwind_llvm_fnptr_type(llvm_cx, + envptr_llvm_type); + + /* now supply _env_api members */ + env_llvm_type->setBody(envptr_llvm_type /*_env_api[0]*/, + unwind_llvm_fnptr_type /*_env_api[1]*/); + + return env_llvm_type; + } /*env_api_llvm_type*/ + + llvm::PointerType * + type2llvm::env_api_llvm_ptr_type(xo::ref::brw llvm_cx) + { + llvm::StructType * env_llvm_type = env_api_llvm_type(llvm_cx); + + return llvm::PointerType::getUnqual(env_llvm_type); + } /*env_api_llvm_ptr_type*/ + +#ifdef NOT_USING + llvm::StructType * + type2llvm::function_td_to_llvm_closure_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + /* closure type doesn't need a name. + * (We might find it convenient to give one anyway) + */ + llvm::StructType * closure_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref()); + + llvm::PointerType * parent_llvm_type + } /*function_td_to_llvm_fnptr_type*/ +#endif + + llvm::StructType * + type2llvm::create_localenv_llvm_type(xo::ref::brw llvm_cx, + xo::ref::brw lambda) + { + llvm::PointerType * parentenvptr_llvm_type = env_api_llvm_ptr_type(llvm_cx); + llvm::PointerType * unwind_llvm_fnptr_type + = type2llvm::require_localenv_unwind_llvm_fnptr_type(llvm_cx, parentenvptr_llvm_type); + + std::vector member_llvm_type_v; + member_llvm_type_v.push_back(parentenvptr_llvm_type); + member_llvm_type_v.push_back(unwind_llvm_fnptr_type); + + for (const auto & var : lambda->argv()) { + if (lambda->is_captured(var->name())) { + /* var needs a slot in localenv_llvm_type for lambda */ + + member_llvm_type_v.push_back(td_to_llvm_type(llvm_cx, + var->valuetype())); + } + } + + /* this type doesn't need a name, right? would be "_" + lambda name + "_localenv" */ + llvm::StructType * localenv_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref()); + + localenv_llvm_type->setBody(member_llvm_type_v, false /*!is_packed*/); + + return localenv_llvm_type; + } /*create_localenv_llvm_type*/ + + } /*namespace jit*/ +} /*namespace xo*/ + +/* end type2llvm.cpp */ From 4c8289336d4a291432f2fa4fcb0f2c113e3bad91 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 7 Jul 2024 16:57:05 -0400 Subject: [PATCH 1229/2524] xo-jit: + primitive wrapper (accept+ignore envptr as 1st argument) --- include/xo/jit/MachPipeline.hpp | 10 +++ include/xo/jit/type2llvm.hpp | 10 ++- src/jit/MachPipeline.cpp | 130 +++++++++++++++++++++++++++----- src/jit/type2llvm.cpp | 8 +- 4 files changed, 138 insertions(+), 20 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 88c9f544..ed81c6c8 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -111,6 +111,16 @@ namespace xo { llvm::Type * codegen_type(TypeDescr td); llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); + + /** like @ref codegen_primitive , but create wrapper function that accepts (and discards) + * environment pointer as first argument. + * + * Implementation consists of tail call to natural primitive, that skips the unused + * environment pointer + **/ + llvm::Function * codegen_primitive_wrapper(ref::brw expr, + llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_apply(ref::brw expr, llvm::IRBuilder<> & ir_builder); /* NOTE: codegen_lambda() needs to be reentrant too. * for example can have a lambda in apply position. diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index 2d532893..dffe155b 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -29,9 +29,17 @@ namespace xo { /** establish llvm representation for a function type * described by @p fn_td + * + * @param wrapper_flag If true, create function type for a wrapper + * to be associated with a closure. + * The wrapper accepts (and ignores) an envapi pointer as first argument. + * Necessary to (for example) support function pointers that may refer + * to either {primitive functions, functions-requiring-closures}, + * with choice deferred until runtime **/ static llvm::FunctionType * function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td); + TypeDescr fn_td, + bool wrapper_flag = false); /** establish llvm concrete representation for a particular lambda's * runtime local environment: diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 3bd895f0..fc44ae88 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -20,6 +20,7 @@ namespace xo { using xo::reflect::Reflect; using xo::reflect::StructMember; using xo::reflect::TypeDescr; + using xo::scope; using llvm::orc::ExecutionSession; using llvm::DataLayout; using std::cerr; @@ -167,7 +168,6 @@ namespace xo { MachPipeline::codegen_primitive(ref::brw expr) { constexpr bool c_debug_flag = true; - using xo::scope; scope log(XO_DEBUG(c_debug_flag)); @@ -249,12 +249,123 @@ namespace xo { return fn; } /*codegen_primitive*/ + llvm::Function * + MachPipeline::codegen_primitive_wrapper(ref::brw expr, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + + scope log(XO_DEBUG(c_debug_flag), + xtag("primitive-name", expr->name())); + + constexpr const char * c_prefix = "w."; + + /* unique name for wrapper. Note we don't allow period in schematica identifiers + * (though we could if we replace . with .. when lowering) + */ + std::string wrap_name = std::string(c_prefix) + expr->name(); + + /* original primitive */ + auto * native_lvfn = codegen_primitive(expr); + + /* wrapped primitive */ + auto * wrap_lvfn = llvm_module_->getFunction(wrap_name); + + if (wrap_lvfn) { + /* wrapper already defined */ + return wrap_lvfn; + } + + TypeDescr fn_td = expr->valuetype(); + + llvm::FunctionType * native_lvtype + = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + + if (!native_lvtype) + return nullptr; + + llvm::FunctionType * wrapper_lvtype + = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), + fn_td, + true /*wrapper_flag (for closure)*/); + + wrap_lvfn = llvm::Function::Create(wrapper_lvtype, + llvm::Function::ExternalLinkage, + wrap_name, + llvm_module_.get()); + + /* at least we know the name of the 1st argument :) */ + auto ix = wrap_lvfn->args().begin(); + ix->setName(".env"); + + auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "entry", wrap_lvfn); + + ir_builder.SetInsertPoint(block); + + std::vector args; + + /* call to native_lvfn, + * forwarding all args of wrap_lvfn, except the first + */ + { + args.reserve(expr->n_arg()); + + int i_wrap_arg = 0; + for (auto & arg : wrap_lvfn->args()) { + if (i_wrap_arg > 0) + args.push_back(&arg); + + ++i_wrap_arg; + } + } + + /* {caller,callee} must agree on calling convention, + * so for primitives we need to assume c. + */ + llvm::CallInst * call = ir_builder.CreateCall(native_lvtype, + native_lvfn, + args, + "w.calltmp"); + if (call) { + call->setTailCall(true); + + /* does this work if call returns void? Is this needed with tail call? */ + ir_builder.CreateRet(call); + + llvm::verifyFunction(*wrap_lvfn); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + wrap_lvfn->print(ss); + + log(xtag("IR-before-opt", buf)); + } + + /* optimize! */ + ir_pipeline_->run_pipeline(*wrap_lvfn); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + wrap_lvfn->print(ss); + + log(xtag("IR-after-opt", buf)); + } + } else { + wrap_lvfn->eraseFromParent(); + wrap_lvfn = nullptr; + } + + return wrap_lvfn; + } /*codegen_primitive_wrapper*/ + llvm::Value * MachPipeline::codegen_apply(ref::brw apply, llvm::IRBuilder<> & ir_builder) { constexpr bool c_debug_flag = true; - using xo::scope; scope log(XO_DEBUG(c_debug_flag), xtag("apply", apply)); @@ -418,7 +529,6 @@ namespace xo { TypeDescr var_type) { constexpr bool c_debug_flag = true; - using xo::scope; scope log(XO_DEBUG(c_debug_flag), xtag("llvm_fn", (void*)llvm_fn), @@ -475,7 +585,6 @@ namespace xo { MachPipeline::codegen_lambda_decl(ref::brw lambda) { constexpr bool c_debug_flag = true; - using xo::scope; scope log(XO_DEBUG(c_debug_flag), xtag("lambda-name", lambda->name())); @@ -491,18 +600,6 @@ namespace xo { /* establish prototype for this function */ -#ifdef OBSOLETE - llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), - lambda->fn_retval()); - - std::vector arg_type_v(lambda->n_arg()); - - for (size_t i = 0, n = lambda->n_arg(); i < n; ++i) { - arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), - lambda->fn_arg(i)); - } -#endif - llvm::FunctionType * llvm_fn_type = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), lambda->valuetype()); @@ -533,7 +630,6 @@ namespace xo { llvm::IRBuilder<> & ir_builder) { constexpr bool c_debug_flag = true; - using xo::scope; scope log(XO_DEBUG(c_debug_flag), xtag("lambda-name", lambda->name())); diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index 23ee48c4..fdfda7d5 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -58,12 +58,16 @@ namespace xo { **/ llvm::FunctionType * type2llvm::function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) + TypeDescr fn_td, + bool wrapper_flag) { int n_fn_arg = fn_td->n_fn_arg(); std::vector llvm_argtype_v; - llvm_argtype_v.reserve(n_fn_arg); + llvm_argtype_v.reserve(n_fn_arg + (wrapper_flag ? 1 : 0)); + + if (wrapper_flag) + llvm_argtype_v.push_back(env_api_llvm_ptr_type(llvm_cx)); /** check function args are all known **/ for (int i = 0; i < n_fn_arg; ++i) { From f2fa9978cf558ade904b083d401c12ed75da9dd0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 7 Jul 2024 18:54:56 -0400 Subject: [PATCH 1230/2524] xo-jit: + unit test for primitive wrapper --- utest/MachPipeline.test.cpp | 61 ++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp index 5e0fafc4..880aabf1 100644 --- a/utest/MachPipeline.test.cpp +++ b/utest/MachPipeline.test.cpp @@ -183,7 +183,66 @@ namespace xo { REQUIRE(actual == expected); } } - } /*TEST_CASE(machpipeline)*/ + } /*TEST_CASE(machpipeline.fptr)*/ + + TEST_CASE("machpipeline.wrap", "[llvm][llvm_closure]") { + constexpr bool c_debug_flag = true; + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipelin.wrap")); + + auto jit = MachPipeline::make(); + + auto root = make_primitive("sqrt", + ::sqrt, + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sqrt); + + llvm::Value * llvm_ircode + = jit->codegen_primitive_wrapper(root, *(jit->llvm_current_ir_builder())); + + /* TODO: printer for llvm::Value* */ + if (llvm_ircode) { + /* note: llvm:errs() is 'raw stderr stream' */ + cerr << "llvm_ircode for primitive wrapper:" << endl; + llvm_ircode->print(llvm::errs()); + cerr << endl; + } else { + cerr << "code generation failed" + << xtag("root", root) + << endl; + } + + REQUIRE(llvm_ircode); + + std::string wrapper_name = std::string("w.") + root->name(); + + jit->machgen_current_module(); + + auto llvm_addr = jit->lookup_symbol(wrapper_name); + + bool llvm_addr_flag = static_cast(llvm_addr); + + if (!llvm_addr_flag) { + cerr << "ex2: lookup: symbol not found" + << xtag("symbol", wrapper_name) + << endl; + } else { + cerr << "ex2: lookup: symbol found" + << xtag("llvm_addr", llvm_addr.get().getValue()) + << xtag("symbol", wrapper_name) + << endl; + } + + REQUIRE(llvm_addr_flag); + + auto fn_ptr = llvm_addr.get().toPtr(); + + REQUIRE(fn_ptr); + + auto actual = (*fn_ptr)(nullptr, 4.0); + + REQUIRE(actual == 2.0); + } rp make_ratio() { From 792dcf015713e9817ab1f3988c82a1bba44850b3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 11:45:58 -0400 Subject: [PATCH 1231/2524] xo-jit: + type2llvm::create_closure_lvtype() --- include/xo/jit/type2llvm.hpp | 24 ++++++++++++++++++++++++ src/jit/type2llvm.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index dffe155b..75f33576 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -41,6 +41,28 @@ namespace xo { TypeDescr fn_td, bool wrapper_flag = false); + /** establish llvm concrete representation for a closure. + * + * +-------+ + * [0] | o-------> fnptr T (*)(envptr, ...) + * +-------+ + * [1] | o-------\ + * +-------+ | + * | + * | + * v + * +-------+ + * parent_env [0] | o-------> _env_api* + * +-------+ + * unwind_fn [1] | o-------> env * (*)(env*, ctl) + * +-------+ + * + * @return struct type. typename will be @c c.foo for lambda with name @c foo + **/ + static llvm::StructType * + create_closure_lvtype(xo::ref::brw llvm_cx, + xo::ref::brw lambda); + /** establish llvm concrete representation for a particular lambda's * runtime local environment: * @@ -62,6 +84,8 @@ namespace xo { * * arg[] comprises the subset of lambda arg names arg[j] for which * lambda->is_captured(arg[j]) is true + * + * @return struct type. typename will be @c e.foo for lambda with name @c foo **/ static llvm::StructType * create_localenv_llvm_type(xo::ref::brw llvm_cx, diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index fdfda7d5..a5405f86 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -303,6 +303,36 @@ namespace xo { return localenv_llvm_type; } /*create_localenv_llvm_type*/ + llvm::StructType * + type2llvm::create_closure_lvtype(xo::ref::brw llvm_cx, + xo::ref::brw lambda) + { + constexpr const char * c_prefix = "c."; + + /* would be precisely correct to use create_localenv_llvm_type() + * here. However judged not sufficiently helpful. + * Would still + * need environment cast whenever closure in apply position is + * not known at compile time. + */ + llvm::PointerType * fn_lvtype = function_td_to_llvm_fnptr_type(llvm_cx, + lambda->valuetype()); + llvm::StructType * env_lvtype = env_api_llvm_type(llvm_cx); + + std::vector member_lvtype_v = { fn_lvtype, env_lvtype }; + + /* e.g. "c.foo" */ + std::string closure_name = std::string(c_prefix) + lambda->name(); + + llvm::StructType * closure_lvtype + = llvm::StructType::create(llvm_cx->llvm_cx_ref(), + member_lvtype_v, + llvm::StringRef(closure_name), + false /*!is_packed*/); + + return closure_lvtype; + } /*create_closure_lvtype*/ + } /*namespace jit*/ } /*namespace xo*/ From 56b924a286ce277db51fb4cc23e220ac761d7c87 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 11:46:18 -0400 Subject: [PATCH 1232/2524] xo-jit: gen lvtype name in type2llvm::create_localenv_llvm_type() --- src/jit/type2llvm.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index a5405f86..231d6de4 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -277,6 +277,8 @@ namespace xo { type2llvm::create_localenv_llvm_type(xo::ref::brw llvm_cx, xo::ref::brw lambda) { + constexpr const char * c_prefix = "e."; + llvm::PointerType * parentenvptr_llvm_type = env_api_llvm_ptr_type(llvm_cx); llvm::PointerType * unwind_llvm_fnptr_type = type2llvm::require_localenv_unwind_llvm_fnptr_type(llvm_cx, parentenvptr_llvm_type); @@ -294,13 +296,16 @@ namespace xo { } } - /* this type doesn't need a name, right? would be "_" + lambda name + "_localenv" */ - llvm::StructType * localenv_llvm_type + /* e.g. "e.foo" */ + std::string env_name = std::string(c_prefix) + lambda->name(); + + llvm::StructType * localenv_lvtype = llvm::StructType::get(llvm_cx->llvm_cx_ref()); - localenv_llvm_type->setBody(member_llvm_type_v, false /*!is_packed*/); + localenv_lvtype->setName(env_name); + localenv_lvtype->setBody(member_llvm_type_v, false /*!is_packed*/); - return localenv_llvm_type; + return localenv_lvtype; } /*create_localenv_llvm_type*/ llvm::StructType * From 19d8a5e846d5b09a2b787b05bdb413963bfc2e40 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 11:47:03 -0400 Subject: [PATCH 1233/2524] xo-jit: doc: + glossary entries --- docs/glossary.rst | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/glossary.rst b/docs/glossary.rst index c5a22ec1..37b98af9 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -4,6 +4,49 @@ Glossary -------- .. glossary:: + c.foo + | llvm typename for automatically-generated closure type for a lambda + | with name `foo`. + + e.foo + | llvm typename for automatically-generated local environment for a + | lambda with name `foo`. + + w.foo + | llvm typename for automatically-generated wrapper function for a + | primitive function `foo`. The wrapper function accepts and ignores + | an extra initial argument supplying an environment pointer. + | + | We apply this practice so that lambdas and primitives support the + | same ABI, so that we can support pointers to abstract functions + | that might at runtime turn out to be either primitives or lambdas + + lambda + | Common use is for lambda to refer to an anonymous function. + | In llvm we need all functions to be named, and those names + | have to be unique. + | + | Since all functions have to be named, we cheerfully adopt + | the oxymoron 'named lambda' + | + | Practices: + | 1. Automatically generate unique names for anonymous lambdas. + | 2. Incorporate user-provided names for convenience, when provided. + | 3. Still have to uniqueify names for user-provided nested lambdas. + + localenv + | Shorthand for local environment. + | Represents an explicit runtime repsentation for a struct that + | holds captured function arguments with the ability to be persisted + | (for example, moved to the the heap). + | + | Note that library `xo-expression` also uses the term environment, but differently. + | In that context describes all function arguments. + + lvtype + | Shorthand for `llvm::Type`: + | llvm-owned representation for a datatype + xsession | Shorthand for `llvm::orc::ExecutionSession`. | Manages running JIT-generated machine code in the host process From 659c0c400b3fe5478d2689bb78078a46260b6005 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 18:31:06 -0400 Subject: [PATCH 1234/2524] xo-jit: refactor: + closures [wip: not tested] --- include/xo/jit/MachPipeline.hpp | 37 +++- include/xo/jit/type2llvm.hpp | 103 ++++++----- src/jit/MachPipeline.cpp | 297 ++++++++++++++++++-------------- src/jit/activation_record.cpp | 129 +++++++------- src/jit/type2llvm.cpp | 90 +++++----- 5 files changed, 376 insertions(+), 280 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index ed81c6c8..4e0714e9 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -121,16 +121,45 @@ namespace xo { llvm::Function * codegen_primitive_wrapper(ref::brw expr, llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen_apply(ref::brw expr, llvm::IRBuilder<> & ir_builder); + /** Generate closure for invoking a primitive function. + * Primitives don't benefit from a closure, but we need a consistent ABI + * to support function-pointer-like behavior for a target function + * that may resolve to primitive-or-lambda at runtime + **/ + llvm::Value * codegen_primitive_closure(ref::brw expr, + llvm::IRBuilder<> & ir_builder); + + llvm::Value * codegen_apply(ref::brw expr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); /* NOTE: codegen_lambda() needs to be reentrant too. * for example can have a lambda in apply position. */ llvm::Function * codegen_lambda_decl(ref::brw expr); llvm::Function * codegen_lambda_defn(ref::brw expr, llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen_variable(ref::brw var, llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen_ifexpr(ref::brw ifexpr, llvm::IRBuilder<> & ir_builder); + /** Generate closure for invoking a lambda (user-defined function). + * See @ref MachPipeline::codegen_apply for invocation + * Same ABI as @ref MachPipeline::codegen_primitive_closure + * + * @param envptr. Environment from surrounding lexical scope. + * This will be captured as envptr member by + * the IR code for creating a closure. + * @ref MachPipeline::codegen_toplevel and friends are responsible for + * assembling and propagating this. + **/ + llvm::Value * codegen_lambda_closure(ref::brw lambda, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_variable(ref::brw var, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_ifexpr(ref::brw ifexpr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen(ref::brw expr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); llvm::Value * codegen_toplevel(ref::brw expr); diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index 75f33576..d7a04f05 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -17,6 +17,7 @@ namespace xo { **/ struct type2llvm { public: + using FunctionInterface = xo::ast::FunctionInterface; using Lambda = xo::ast::Lambda; using TypeDescr = xo::reflect::TypeDescr; @@ -57,11 +58,55 @@ namespace xo { * unwind_fn [1] | o-------> env * (*)(env*, ctl) * +-------+ * - * @return struct type. typename will be @c c.foo for lambda with name @c foo + * @return struct type. typename will be @c c.foo for a function + * (primitive or lambda) with name @c foo **/ static llvm::StructType * - create_closure_lvtype(xo::ref::brw llvm_cx, - xo::ref::brw lambda); + create_closureapi_lvtype(xo::ref::brw llvm_cx, + xo::ref::brw fn); + + /** establish llvm abstract representation for a closure: + * struct with + * - [0] function pointer + * - [1] runtime localenv pointer + * + * +-------+ + * | o---------> native function + * +-------+ + * | o---------> runtime localenv + * +-------+ (possibly nullptr) + * + * 1. for primitives, localenv will be null pointer + * 2. for lambdas L with L->requires_closure_flag() = false, + * localenv will also be null pointer + * 3. for lambdas with L->requires_closure_flag() = true, + * + * localenv will (for lambdas requiring closures) + * in practice be struct: + * + * ^ + * | parent + * +-------+ | + * parent_env [0] | o-------/ + * +-------+ + * unwind_fn [1] | o-------> env * (*)(env*, ctl) + * +-------+ + * arg[i] [2+i] . ... . + * . ... . + * +-------+ + * + * ctl=0 unwind. finalization for any arg[i] that requires it. + * returns nullptr + * ctl=1 copy. copy runtime environment to heap destination + * and return address of the copy + * + * Implementation here will just use generic pointer for runtime + * localenv. + **/ + static llvm::StructType * + function_td_to_closureapi_lvtype(xo::ref::brw llvm_cx, + TypeDescr fn_td, + const std::string & hint_name); /** establish llvm concrete representation for a particular lambda's * runtime local environment: @@ -128,9 +173,17 @@ namespace xo { private: /** establish llvm representation for a function-pointer type * described by @p fn_td + * + * @param wrapper_flag If true, create function type for a wrapper + * to be associated with a closure. + * The wrapper accepts (and ignores) an envapi pointer as first argument. + * Necessary to (for example) support function pointers that may refer + * to either {primitive functions, functions-requiring-closures}, + * with choice deferred until runtime **/ static llvm::PointerType * function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td); + TypeDescr fn_td, + bool wrapper_flag); /** establish llvm representation for a struct type described by @p struct_td **/ @@ -164,48 +217,6 @@ namespace xo { static llvm::StructType * env_api_llvm_type(xo::ref::brw llvm_cx); - /** establish llvm abstract representation for a closure: - * struct with - * - [0] function pointer - * - [1] runtime localenv pointer - * - * +-------+ - * | o---------> native function - * +-------+ - * | o---------> runtime localenv - * +-------+ (possibly nullptr) - * - * 1. for primitives, localenv will be null pointer - * 2. for lambdas L with L->requires_closure_flag() = false, - * localenv will also be null pointer - * 3. for lambdas with L->requires_closure_flag() = true, - * - * localenv will (for lambdas requiring closures) - * in practice be struct: - * - * ^ - * | parent - * +-------+ | - * parent_env [0] | o-------/ - * +-------+ - * unwind_fn [1] | o-------> env * (*)(env*, ctl) - * +-------+ - * arg[i] [2+i] . ... . - * . ... . - * +-------+ - * - * ctl=0 unwind. finalization for any arg[i] that requires it. - * returns nullptr - * ctl=1 copy. copy runtime environment to heap destination - * and return address of the copy - * - * Implementation here will just use generic pointer for runtime - * localenv. - **/ - static llvm::StructType * - function_td_to_llvm_closure_type(xo::ref::brw llvm_cx, - TypeDescr fn_td); - }; /*type2llvm*/ } /*namespace jit*/ } /*namespace xo*/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index fc44ae88..b458f9fb 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -361,8 +361,29 @@ namespace xo { return wrap_lvfn; } /*codegen_primitive_wrapper*/ + llvm::Value * + MachPipeline::codegen_primitive_closure(ref::brw expr, + llvm::IRBuilder<> & ir_builder) + { + llvm::StructType * closure_lvtype + = type2llvm::create_closureapi_lvtype(llvm_cx_.borrow(), expr); + + llvm::Function * pm_wrapper = codegen_primitive_wrapper(expr, ir_builder); + llvm::Value * env_0ptr = llvm::ConstantPointerNull::get(type2llvm::env_api_llvm_ptr_type(llvm_cx_)); + + llvm::Value * lv_closure = nullptr; + + lv_closure = ir_builder.CreateInsertValue(llvm::UndefValue::get(closure_lvtype), + pm_wrapper, {0}, "wrapfnptr" /*name*/); + lv_closure = ir_builder.CreateInsertValue(lv_closure, + env_0ptr, {1}, "nullenvptr" /*name*/); + + return lv_closure; + } /*codegen_primitive_closure*/ + llvm::Value * MachPipeline::codegen_apply(ref::brw apply, + llvm::Value * envptr, llvm::IRBuilder<> & ir_builder) { constexpr bool c_debug_flag = true; @@ -376,14 +397,14 @@ namespace xo { using std::cerr; using std::endl; - /* IR for value in function position. - * Although it will generate a function (or pointer-to-function), - * it need not have inherited type llvm::Function. + /* IR for closure in function position + * see: + * - MachPipeline::codegen_primitive_closure + * - MachPipeline::codegen_lambda_closure + * - type2llvm::create_closure_lvtype */ - llvm::Value * llvm_fnval = nullptr; + llvm::Value * llvm_closure = nullptr; llvmintrinsic intrinsic = llvmintrinsic::invalid; - /* function type in apply node's function position */ - TypeDescr ast_fn_td = apply->fn()->valuetype(); { /* special treatement for primitive in apply position: * allows substituting LLVM intrinsic @@ -392,12 +413,12 @@ namespace xo { auto pm = PrimitiveInterface::from(apply->fn()); if (pm) { - llvm_fnval = this->codegen_primitive(pm); + llvm_closure = this->codegen_primitive(pm); /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ intrinsic = pm->intrinsic(); } } else { - llvm_fnval = this->codegen(apply->fn(), ir_builder); + llvm_closure = this->codegen(apply->fn(), envptr, ir_builder); /* we don't need any special checking here. * already know (from xo-level checking) that pointer has the right type. @@ -414,10 +435,13 @@ namespace xo { } } - if (!llvm_fnval) { + if (!llvm_closure) { return nullptr; } + /* function type in apply node's function position */ + TypeDescr ast_fn_td = apply->fn()->valuetype(); + #ifdef NOT_USING_DEBUG cerr << "MachPipeline::codegen_apply: fn:" << endl; fn->print(llvm::errs()); @@ -452,12 +476,50 @@ namespace xo { } #endif + llvm::StructType * closure_lvtype + = type2llvm::function_td_to_closureapi_lvtype(llvm_cx_, + ast_fn_td, + "" /*name - not required*/); + + llvm::Value * lv_fnptr = nullptr; + { + llvm::Value * fnptr_slot + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, 0 /*value*/)); + + std::array index_v = {{fnptr_slot /*fnptr slot = closure[0]*/}}; + + lv_fnptr = ir_builder.CreateInBoundsGEP(closure_lvtype, + llvm_closure, + index_v); + } + + llvm::Value * lv_fnenvptr = nullptr; + { + llvm::Value * envptr_slot + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, 1 /*value*/)); + + std::array index_v = {{envptr_slot /*envptr slot = closure[1]*/}}; + + lv_fnenvptr = ir_builder.CreateInBoundsGEP(closure_lvtype, + llvm_closure, + index_v); + } + std::vector args; - args.reserve(apply->argv().size()); + /* +1 for envptr */ + args.reserve(1 + apply->argv().size()); + + + /* we must take envptr from closure, + * and we need to do this using some version of getelementptr + */ + args.push_back(lv_fnenvptr); int i = 0; for (const auto & arg_expr : apply->argv()) { - auto * arg = this->codegen(arg_expr, ir_builder); + auto * arg = this->codegen(arg_expr, envptr, ir_builder); if (log) { /* TODO: print helper for llvm::Value* */ @@ -476,26 +538,28 @@ namespace xo { /* if we have an intrinsic hint, * then instead of invoking a function, * we use some native machine instruction instead. + * + * args[0] not used here, that holds envptr from faux closure */ switch(intrinsic) { case llvmintrinsic::i_neg: - return ir_builder.CreateNeg(args[0]); + return ir_builder.CreateNeg(args[1]); case llvmintrinsic::i_add: - return ir_builder.CreateAdd(args[0], args[1]); + return ir_builder.CreateAdd(args[1], args[2]); case llvmintrinsic::i_sub: - return ir_builder.CreateSub(args[0], args[1]); + return ir_builder.CreateSub(args[1], args[2]); case llvmintrinsic::i_mul: - return ir_builder.CreateMul(args[0], args[1]); + return ir_builder.CreateMul(args[1], args[2]); case llvmintrinsic::i_sdiv: - return ir_builder.CreateSDiv(args[0], args[1]); + return ir_builder.CreateSDiv(args[1], args[2]); case llvmintrinsic::i_udiv: - return ir_builder.CreateUDiv(args[0], args[1]); + return ir_builder.CreateUDiv(args[1], args[2]); case llvmintrinsic::fp_add: - return ir_builder.CreateFAdd(args[0], args[1]); + return ir_builder.CreateFAdd(args[1], args[2]); case llvmintrinsic::fp_mul: - return ir_builder.CreateFMul(args[0], args[1]); + return ir_builder.CreateFMul(args[1], args[2]); case llvmintrinsic::fp_div: - return ir_builder.CreateFDiv(args[0], args[1]); + return ir_builder.CreateFDiv(args[1], args[2]); case llvmintrinsic::invalid: case llvmintrinsic::fp_sqrt: case llvmintrinsic::fp_pow: @@ -506,65 +570,18 @@ namespace xo { break; } - /* At least as of 18.1.5, LLVM needs us to supply function type - * when making a function call. In particular it doesn't remember - * the function type with each function pointer - */ - llvm::FunctionType * llvm_fn_type - = type2llvm::function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); + = type2llvm::function_td_to_llvm_type(this->llvm_cx_, + ast_fn_td, + true /*wrapper_flag*/); return ir_builder.CreateCall(llvm_fn_type, - llvm_fnval, + lv_fnptr, args, "calltmp"); } /*codegen_apply*/ -#ifdef OBSOLETE - /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ - llvm::AllocaInst * - MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, - const std::string & var_name, - TypeDescr var_type) - { - constexpr bool c_debug_flag = true; - - scope log(XO_DEBUG(c_debug_flag), - xtag("llvm_fn", (void*)llvm_fn), - xtag("var_name", var_name), - xtag("var_type", var_type->short_name())); - - llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), - llvm_fn->getEntryBlock().begin()); - - llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx_.borrow(), - var_type); - - log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); - if (log) { - std::string llvm_var_type_str; - llvm::raw_string_ostream ss(llvm_var_type_str); - llvm_var_type->print(ss); - - log(xtag("llvm_var_type", llvm_var_type_str)); - } - - if (!llvm_var_type) - return nullptr; - - llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(llvm_var_type, - nullptr, - var_name); - log && log(xtag("alloca", (void*)retval), - xtag("align", retval->getAlign().value()), - xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); - - return retval; - } /*create_entry_block_alloca*/ -#endif - - std::vector> MachPipeline::find_lambdas(ref::brw expr) const { @@ -600,24 +617,40 @@ namespace xo { /* establish prototype for this function */ + /* wrapper_flag: llvm function type takes extra first argument, + * supplying environment pointer from surrounding closure. + * + * Note that this argument is not present in lambda, + * so we need care. lambda->fn_arg(i) -> lvfn->arg [i+1] + */ llvm::FunctionType * llvm_fn_type = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), - lambda->valuetype()); + lambda->valuetype(), + true /*wrapper_flag*/); /* create (initially empty) function */ fn = llvm::Function::Create(llvm_fn_type, llvm::Function::ExternalLinkage, lambda->name(), llvm_module_.get()); - /* also capture argument names */ + + /* also adopt lambda's formal argument names */ { int i = 0; for (auto & arg : fn->args()) { - log && log("llvm formal param names", - xtag("i", i), - xtag("param", lambda->argv().at(i))); + if (i == 0) { + log && log("llvm inserted env param", + xtag("i", i)); + + arg.setName(".env"); + } else { + log && log("llvm formal param names", + xtag("i", i), + xtag("param", lambda->argv().at(i-1))); + + arg.setName(lambda->argv().at(i-1)->name()); + } - arg.setName(lambda->argv().at(i)->name()); ++i; } } @@ -648,6 +681,10 @@ namespace xo { return nullptr; } + /* environment for this lambda's clsoure + * passed as extra 1st argument + */ + llvm::Value * envptr = llvm_fn->args().begin(); /* generate function body */ @@ -667,45 +704,9 @@ namespace xo { return nullptr; } -#ifdef OBSOLETE - { - log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); - - int i = 0; - for (auto & arg : llvm_fn->args()) { - log && log("nested environment", - xtag("i", i), - xtag("param", std::string(arg.getName()))); - - std::string arg_name = std::string(arg.getName()); - - /* stack location for arg[i] */ - llvm::AllocaInst * alloca - = create_entry_block_alloca(llvm_fn, - arg_name, - lambda->fn_arg(i)); - - if (!alloca) { - this->env_stack_.pop(); - return nullptr; - } - - /* store on function entry - * see codegen_variable() for corresponding load - */ - ir_builder.CreateStore(&arg, alloca); - - /* remember stack location for reference + assignment - * in lambda body. - * - */ - env_stack_.top().alloc_var(i, arg_name, alloca); - ++i; - } - } -#endif - - llvm::Value * retval = this->codegen(lambda->body(), ir_builder); + llvm::Value * retval = this->codegen(lambda->body(), + envptr, + ir_builder); if (retval) { /* completes the function.. */ @@ -746,10 +747,33 @@ namespace xo { return llvm_fn; } /*codegen_lambda_defn*/ + llvm::Value * + MachPipeline::codegen_lambda_closure(ref::brw lambda, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder) + { + llvm::StructType * closure_lvtype + = type2llvm::create_closureapi_lvtype(llvm_cx_.borrow(), lambda); + + llvm::Function * lvfn = codegen_lambda_decl(lambda); + + llvm::Value * lv_closure = nullptr; + + lv_closure = ir_builder.CreateInsertValue(llvm::UndefValue::get(closure_lvtype), + lvfn, {0}, "lmfnptr" /*name*/); + lv_closure = ir_builder.CreateInsertValue(lv_closure, + envptr, {1}, "envptr" /*name*/); + + return lv_closure; + } /*codegen_lambda_closure*/ + llvm::Value * MachPipeline::codegen_variable(ref::brw var, + llvm::Value * /*envptr*/, llvm::IRBuilder<> & ir_builder) { + /* TODO: navigate envptr to handle non-local variables */ + if (env_stack_.empty()) { cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" << xtag("x", var->name()) @@ -772,9 +796,11 @@ namespace xo { } /*codegen_variable*/ llvm::Value * - MachPipeline::codegen_ifexpr(ref::brw expr, llvm::IRBuilder<> & ir_builder) + MachPipeline::codegen_ifexpr(ref::brw expr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder) { - llvm::Value * test_ir = this->codegen(expr->test(), ir_builder); + llvm::Value * test_ir = this->codegen(expr->test(), envptr, ir_builder); /** need test result in a variable **/ llvm::Value * test_with_cmp_ir @@ -813,6 +839,7 @@ namespace xo { ir_builder.SetInsertPoint(when_true_bb); llvm::Value * when_true_ir = this->codegen(expr->when_true(), + envptr, ir_builder); if (!when_true_ir) @@ -827,7 +854,9 @@ namespace xo { parent_fn->insert(parent_fn->end(), when_false_bb); ir_builder.SetInsertPoint(when_false_bb); - llvm::Value * when_false_ir = this->codegen(expr->when_false(), ir_builder); + llvm::Value * when_false_ir = this->codegen(expr->when_false(), + envptr, + ir_builder); if (!when_false_ir) return nullptr; @@ -852,21 +881,24 @@ namespace xo { } /*codegen_ifexpr*/ llvm::Value * - MachPipeline::codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder) + MachPipeline::codegen(ref::brw expr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder) { switch(expr->extype()) { case exprtype::constant: return this->codegen_constant(ConstantInterface::from(expr)); case exprtype::primitive: - return this->codegen_primitive(PrimitiveInterface::from(expr)); + return this->codegen_primitive_closure(PrimitiveInterface::from(expr), ir_builder); case exprtype::apply: - return this->codegen_apply(Apply::from(expr), ir_builder); + return this->codegen_apply(Apply::from(expr), envptr, ir_builder); case exprtype::lambda: - return this->codegen_lambda_decl(Lambda::from(expr)); + return this->codegen_lambda_closure(Lambda::from(expr), envptr, ir_builder); + //return this->codegen_lambda_decl(Lambda::from(expr)); case exprtype::variable: - return this->codegen_variable(Variable::from(expr), ir_builder); + return this->codegen_variable(Variable::from(expr), envptr, ir_builder); case exprtype::ifexpr: - return this->codegen_ifexpr(IfExpr::from(expr), ir_builder); + return this->codegen_ifexpr(IfExpr::from(expr), envptr, ir_builder); case exprtype::invalid: case exprtype::n_expr: return nullptr; @@ -910,6 +942,8 @@ namespace xo { this->codegen_lambda_decl(lambda); } +#ifdef OBSOLETE /* don't do this anymore, obscures lexical context */ + /* Pass 2 */ for (auto lambda : fn_v) { this->codegen_lambda_defn(lambda, @@ -931,6 +965,19 @@ namespace xo { return this->codegen(expr, *(this->llvm_toplevel_ir_builder_.get())); } +#endif + + /* 1. using nullptr as runtime representation for global environment + * 2. may have to elaborate this later? not clear to me + */ + + llvm::Value * env_0ptr + = (llvm::ConstantPointerNull::get + (type2llvm::env_api_llvm_ptr_type(llvm_cx_))); + + return this->codegen(expr, + env_0ptr, + *(this->llvm_toplevel_ir_builder_.get())); } /*codegen_toplevel*/ void diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index 413412af..be1275ef 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -155,7 +155,7 @@ namespace xo { = llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(), llvm::APInt(32 /*bits*/, i_slot /*value*/)); - std::array index_v = { + std::array index_v = { {i32_slot /*environment slot #0*/}}; llvm::Value * llvm_localenv_slot_ptr @@ -194,37 +194,44 @@ namespace xo { { int i_arg = 0; for (auto & arg : llvm_fn->args()) { - std::string arg_name = std::string(arg.getName()); - - log && log("nested environment", - xtag("i", i_arg), - xtag("arg[i]", arg_name), - xtag("stackonly(i)", binding_v_[i_arg].is_stackonly())); - - if (binding_v_[i_arg].is_stackonly()) { - /* stack location for arg[i] */ - runtime_binding_detail binding - = create_entry_block_alloca(llvm_cx, - //data_layout, - llvm_fn, - tmp_ir_builder, - i_arg, - arg_name, - lambda_->fn_arg(i_arg)); - - if (!binding.llvm_addr_) - return false; - - /* store on function entry - * see codegen_variable() for corresponding load + if (i_arg == 0) { + /* 1st argument is injected environment pointer. + * we don't need that to be on the stack, + * since not modifiable and not user-referencable. */ - ir_builder.CreateStore(&arg, binding.llvm_addr_); + } else { + std::string arg_name = std::string(arg.getName()); - /* remember stack location for reference + assignment - * in lambda body. - * - */ - this->alloc_var(arg_name, binding); + log && log("nested environment", + xtag("i", i_arg), + xtag("arg[i]", arg_name), + xtag("stackonly(i)", binding_v_[i_arg-1].is_stackonly())); + + if (binding_v_[i_arg-1].is_stackonly()) { + /* stack location for arg[i] */ + runtime_binding_detail binding + = create_entry_block_alloca(llvm_cx, + //data_layout, + llvm_fn, + tmp_ir_builder, + i_arg, + arg_name, + lambda_->fn_arg(i_arg)); + + if (!binding.llvm_addr_) + return false; + + /* store on function entry + * see codegen_variable() for corresponding load + */ + ir_builder.CreateStore(&arg, binding.llvm_addr_); + + /* remember stack location for reference + assignment + * in lambda body. + * + */ + this->alloc_var(arg_name, binding); + } } ++i_arg; @@ -297,7 +304,7 @@ namespace xo { int i_localenv_slot = 0; /* store localenv->parent_env */ - { + { llvm::Value * slot_addr = runtime_localenv_slot_addr(llvm_cx, localenv_llvm_type, @@ -348,42 +355,48 @@ namespace xo { int i_arg = 0; for (llvm::Argument & arg : llvm_fn->args()) { - std::string arg_name = std::string(arg.getName()); - - log && log("nested environment", - xtag("i", i_arg), - xtag("arg[i]", arg_name), - xtag("captured(i)", binding_v_[i_arg].is_captured())); - - if (binding_v_[i_arg].is_captured()) { - // do something with runtime-local-env for this llvm_fn - - /* remember stack location for reference + assignment - * in lambda body. - * + if (i_arg == 0) { + /* to remove all doubt, ignore first arg here. + * it's non-captureable environment pointer */ + } else { + std::string arg_name = std::string(arg.getName()); - TypeDescr arg_td = lambda_->fn_arg(i_arg); + log && log("nested environment", + xtag("i", i_arg), + xtag("arg[i]", arg_name), + xtag("captured(i)", binding_v_[i_arg-1].is_captured())); - llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, arg_td); + if (binding_v_[i_arg-1].is_captured()) { + // do something with runtime-local-env for this llvm_fn - llvm::Value * slot_addr - = runtime_localenv_slot_addr(llvm_cx, - localenv_llvm_type, - localenv_alloca, - i_localenv_slot, - tmp_ir_builder); + /* remember stack location for reference + assignment + * in lambda body. + * + */ - if (!slot_addr) - return false; + TypeDescr arg_td = lambda_->fn_arg(i_arg-1); - ++i_localenv_slot; + llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, arg_td); - tmp_ir_builder.CreateStore(&arg, slot_addr); + llvm::Value * slot_addr + = runtime_localenv_slot_addr(llvm_cx, + localenv_llvm_type, + localenv_alloca, + i_localenv_slot, + tmp_ir_builder); - runtime_binding_detail binding = { i_arg, slot_addr, llvm_var_type }; + if (!slot_addr) + return false; - this->alloc_var(arg_name, binding); + ++i_localenv_slot; + + tmp_ir_builder.CreateStore(&arg, slot_addr); + + runtime_binding_detail binding = { i_arg, slot_addr, llvm_var_type }; + + this->alloc_var(arg_name, binding); + } } ++i_arg; diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index 231d6de4..6a982b89 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -26,7 +26,8 @@ namespace xo { /* in this context, we're looking for a representation for a value, * i.e. something that can be stored in a variable */ - return function_td_to_llvm_fnptr_type(llvm_cx, td); + //return function_td_to_llvm_fnptr_type(llvm_cx, td); + return function_td_to_closureapi_lvtype(llvm_cx, td, ""); } else if (td->is_struct()) { return struct_td_to_llvm_type(llvm_cx, td); } else if (td->is_pointer()) { @@ -95,9 +96,10 @@ namespace xo { llvm::PointerType * type2llvm::function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) + TypeDescr fn_td, + bool wrapper_flag) { - auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); + auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td, wrapper_flag); /** like C: llvm IR doesn't support function-valued variables; * it does however support pointer-to-function-valued variables @@ -227,7 +229,7 @@ namespace xo { /* _env_api: base type for a local environment */ llvm::StructType * env_llvm_type = llvm::StructType::get(llvm_cx->llvm_cx_ref(), - "_env_api"); + "_env_api"); /* _env_api[0]: pointer to a local environment */ llvm::PointerType * envptr_llvm_type @@ -253,25 +255,48 @@ namespace xo { return llvm::PointerType::getUnqual(env_llvm_type); } /*env_api_llvm_ptr_type*/ -#ifdef NOT_USING llvm::StructType * - type2llvm::function_td_to_llvm_closure_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) + type2llvm::create_closureapi_lvtype(xo::ref::brw llvm_cx, + xo::ref::brw fn) { - constexpr bool c_debug_flag = true; - using xo::scope; + constexpr const char * c_prefix = "c."; - scope log(XO_DEBUG(c_debug_flag)); + /* e.g. "c.foo" */ + std::string closure_name = std::string(c_prefix) + fn->name(); - /* closure type doesn't need a name. - * (We might find it convenient to give one anyway) + return function_td_to_closureapi_lvtype(llvm_cx, + fn->valuetype(), + closure_name); + } /*create_closureapi_lvtype*/ + + llvm::StructType * + type2llvm::function_td_to_closureapi_lvtype(xo::ref::brw llvm_cx, + TypeDescr fn_td, + const std::string & hint_name) + { + /* would be precisely correct to use create_localenv_llvm_type() + * here. However judged not sufficiently helpful. + * Would still + * need environment cast whenever closure in apply position is + * not known at compile time. */ - llvm::StructType * closure_llvm_type - = llvm::StructType::get(llvm_cx->llvm_cx_ref()); + llvm::PointerType * fn_lvtype = function_td_to_llvm_fnptr_type(llvm_cx, + fn_td, + true /*wrapper_flag*/); + llvm::StructType * env_lvtype = env_api_llvm_type(llvm_cx); - llvm::PointerType * parent_llvm_type - } /*function_td_to_llvm_fnptr_type*/ -#endif + std::vector member_lvtype_v = { fn_lvtype, env_lvtype }; + + llvm::StructType * closure_lvtype + = llvm::StructType::get(llvm_cx->llvm_cx_ref(), member_lvtype_v); + + //closure_lvtype->setBody(member_lvtype_v); + + if (!hint_name.empty()) + closure_lvtype->setName(llvm::StringRef(hint_name)); + + return closure_lvtype; + } /*function_td_to_closureapi_lvtype*/ llvm::StructType * type2llvm::create_localenv_llvm_type(xo::ref::brw llvm_cx, @@ -289,7 +314,7 @@ namespace xo { for (const auto & var : lambda->argv()) { if (lambda->is_captured(var->name())) { - /* var needs a slot in localenv_llvm_type for lambda */ + /* var is captured -> needs a slot in the localenv_llvm_type belonging to this lambda */ member_llvm_type_v.push_back(td_to_llvm_type(llvm_cx, var->valuetype())); @@ -308,35 +333,6 @@ namespace xo { return localenv_lvtype; } /*create_localenv_llvm_type*/ - llvm::StructType * - type2llvm::create_closure_lvtype(xo::ref::brw llvm_cx, - xo::ref::brw lambda) - { - constexpr const char * c_prefix = "c."; - - /* would be precisely correct to use create_localenv_llvm_type() - * here. However judged not sufficiently helpful. - * Would still - * need environment cast whenever closure in apply position is - * not known at compile time. - */ - llvm::PointerType * fn_lvtype = function_td_to_llvm_fnptr_type(llvm_cx, - lambda->valuetype()); - llvm::StructType * env_lvtype = env_api_llvm_type(llvm_cx); - - std::vector member_lvtype_v = { fn_lvtype, env_lvtype }; - - /* e.g. "c.foo" */ - std::string closure_name = std::string(c_prefix) + lambda->name(); - - llvm::StructType * closure_lvtype - = llvm::StructType::create(llvm_cx->llvm_cx_ref(), - member_lvtype_v, - llvm::StringRef(closure_name), - false /*!is_packed*/); - - return closure_lvtype; - } /*create_closure_lvtype*/ } /*namespace jit*/ } /*namespace xo*/ From 26a055eb1c526c82e468f43437829da88c472eeb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 18:31:37 -0400 Subject: [PATCH 1235/2524] xo-jit: docs: ++ ABI in glossary --- docs/glossary.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/glossary.rst b/docs/glossary.rst index 37b98af9..6af7073a 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -4,6 +4,10 @@ Glossary -------- .. glossary:: + ABI + | Short for Application Binary Interface. + | In this context applies to conventions adopted by `xo-jit`. + c.foo | llvm typename for automatically-generated closure type for a lambda | with name `foo`. From 09f5c141dfd9eaf8561d6472a1f10a31cc8c5470 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 10 Jul 2024 16:05:00 -0400 Subject: [PATCH 1236/2524] xo-jit: fnptr -> closures for primitives+lambdas throughout --- include/xo/jit/activation_record.hpp | 11 ++ include/xo/jit/type2llvm.hpp | 33 ++-- src/jit/MachPipeline.cpp | 233 +++++++++++++++++---------- src/jit/activation_record.cpp | 39 ++++- src/jit/type2llvm.cpp | 92 +++++++++-- utest/MachPipeline.test.cpp | 11 +- 6 files changed, 295 insertions(+), 124 deletions(-) diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index 82763e6c..e3eb7a66 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -68,6 +68,17 @@ namespace xo { llvm::Type * llvm_type_ = nullptr; }; + inline std::ostream & + operator<<(std::ostream & os, const runtime_binding_detail & x) { + os << ""; + + return os; + } + /** * 1. pattern for a stack frame associated with a user-defined function (some Lambda lm) * diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index d7a04f05..35a6e739 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -38,9 +38,23 @@ namespace xo { * to either {primitive functions, functions-requiring-closures}, * with choice deferred until runtime **/ - static llvm::FunctionType * function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td, - bool wrapper_flag = false); + static llvm::FunctionType * function_td_to_lvtype(xo::ref::brw llvm_cx, + TypeDescr fn_td, + bool wrapper_flag = false); + + /** establish llvm representation for a function-pointer type + * described by @p fn_td + * + * @param wrapper_flag If true, create function type for a wrapper + * to be associated with a closure. + * The wrapper accepts (and ignores) an envapi pointer as first argument. + * Necessary to (for example) support function pointers that may refer + * to either {primitive functions, functions-requiring-closures}, + * with choice deferred until runtime + **/ + static llvm::PointerType * function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td, + bool wrapper_flag); /** establish llvm concrete representation for a closure. * @@ -171,19 +185,6 @@ namespace xo { llvm::PointerType * hint_envptr_llvm_type = nullptr); private: - /** establish llvm representation for a function-pointer type - * described by @p fn_td - * - * @param wrapper_flag If true, create function type for a wrapper - * to be associated with a closure. - * The wrapper accepts (and ignores) an envapi pointer as first argument. - * Necessary to (for example) support function pointers that may refer - * to either {primitive functions, functions-requiring-closures}, - * with choice deferred until runtime - **/ - static llvm::PointerType * function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td, - bool wrapper_flag); /** establish llvm representation for a struct type described by @p struct_td **/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index b458f9fb..671f38fa 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -192,7 +192,7 @@ namespace xo { TypeDescr fn_td = expr->valuetype(); llvm::FunctionType * llvm_fn_type - = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + = type2llvm::function_td_to_lvtype(llvm_cx_.borrow(), fn_td); if (!llvm_fn_type) return nullptr; @@ -251,7 +251,7 @@ namespace xo { llvm::Function * MachPipeline::codegen_primitive_wrapper(ref::brw expr, - llvm::IRBuilder<> & ir_builder) + llvm::IRBuilder<> & /*ir_builder*/) { constexpr bool c_debug_flag = true; @@ -266,7 +266,7 @@ namespace xo { std::string wrap_name = std::string(c_prefix) + expr->name(); /* original primitive */ - auto * native_lvfn = codegen_primitive(expr); + auto * native_lvfn = this->codegen_primitive(expr); /* wrapped primitive */ auto * wrap_lvfn = llvm_module_->getFunction(wrap_name); @@ -279,13 +279,15 @@ namespace xo { TypeDescr fn_td = expr->valuetype(); llvm::FunctionType * native_lvtype - = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + = type2llvm::function_td_to_lvtype(llvm_cx_.borrow(), + fn_td, + false /*!wrapper_flag*/); if (!native_lvtype) return nullptr; llvm::FunctionType * wrapper_lvtype - = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), + = type2llvm::function_td_to_lvtype(llvm_cx_.borrow(), fn_td, true /*wrapper_flag (for closure)*/); @@ -301,7 +303,11 @@ namespace xo { auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", wrap_lvfn); - ir_builder.SetInsertPoint(block); + /* don't call SetInsertPoint() on incoming ir_builder argument. + * Want to avoid disturbing top-to-bottom flow + */ + llvm::IRBuilder<> tmp_ir_builder(llvm_cx_->llvm_cx_ref()); + tmp_ir_builder.SetInsertPoint(block); std::vector args; @@ -323,15 +329,15 @@ namespace xo { /* {caller,callee} must agree on calling convention, * so for primitives we need to assume c. */ - llvm::CallInst * call = ir_builder.CreateCall(native_lvtype, - native_lvfn, - args, - "w.calltmp"); + llvm::CallInst * call = tmp_ir_builder.CreateCall(native_lvtype, + native_lvfn, + args, + "w.calltmp"); if (call) { call->setTailCall(true); /* does this work if call returns void? Is this needed with tail call? */ - ir_builder.CreateRet(call); + tmp_ir_builder.CreateRet(call); llvm::verifyFunction(*wrap_lvfn); @@ -365,6 +371,9 @@ namespace xo { MachPipeline::codegen_primitive_closure(ref::brw expr, llvm::IRBuilder<> & ir_builder) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + llvm::StructType * closure_lvtype = type2llvm::create_closureapi_lvtype(llvm_cx_.borrow(), expr); @@ -402,6 +411,8 @@ namespace xo { * - MachPipeline::codegen_primitive_closure * - MachPipeline::codegen_lambda_closure * - type2llvm::create_closure_lvtype + * + * although this refers to a closure, llvm doesn't know that */ llvm::Value * llvm_closure = nullptr; llvmintrinsic intrinsic = llvmintrinsic::invalid; @@ -413,7 +424,7 @@ namespace xo { auto pm = PrimitiveInterface::from(apply->fn()); if (pm) { - llvm_closure = this->codegen_primitive(pm); + llvm_closure = this->codegen_primitive_closure(pm, ir_builder); /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ intrinsic = pm->intrinsic(); } @@ -442,69 +453,93 @@ namespace xo { /* function type in apply node's function position */ TypeDescr ast_fn_td = apply->fn()->valuetype(); -#ifdef NOT_USING_DEBUG - cerr << "MachPipeline::codegen_apply: fn:" << endl; - fn->print(llvm::errs()); - cerr << endl; -#endif + if (log) { + log("MachPipeline::codegen_apply: fn in apply pos..."); + llvm_closure->print(llvm::errs()); + log("...done"); + log("llvm type..."); + llvm_closure->getType()->dump(); + log("...done"); + } /* checks here will be redundant */ -#ifdef REDUNDANT_TYPECHECK - if (apply->argv().size() != ast_fn_td->n_fn_arg()) { - cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" - //<< xtag("f", ast_fn->name()) - << xtag("n1", ast_fn_td->n_fn_arg()) - << xtag("n2", apply->argv().size()) - << endl; - - return nullptr; - } - - /** also check argument types **/ - for (size_t i = 0, n = ast_fn_td->n_fn_arg(); i < n; ++i) { - if (apply->argv()[i]->valuetype() != ast_fn_td->fn_arg(i)) { - cerr << "MachPipeline::codegen_apply: error: callee F for arg# I seeeing U instead of expected T" - << xtag("F", apply->fn()) - << xtag("I", i) - << xtag("U", apply->argv()[i]->valuetype()->short_name()) - << xtag("T", ast_fn_td->fn_arg(i)->short_name()) - << endl; - - return nullptr; - } - } -#endif - +#ifdef OBSOLETE llvm::StructType * closure_lvtype = type2llvm::function_td_to_closureapi_lvtype(llvm_cx_, ast_fn_td, "" /*name - not required*/); +#endif llvm::Value * lv_fnptr = nullptr; { +#ifdef MAYBE_VERBOSE + llvm::Value * i0_slot + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, 0 /*value*/)); + llvm::Value * fnptr_slot = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), llvm::APInt(32 /*bits*/, 0 /*value*/)); - std::array index_v = {{fnptr_slot /*fnptr slot = closure[0]*/}}; + std::array index_v + = {{i0_slot, + fnptr_slot /*fnptr slot = closure[0]*/}}; - lv_fnptr = ir_builder.CreateInBoundsGEP(closure_lvtype, - llvm_closure, - index_v); + llvm::Value * lv_fnptr_addr + = ir_builder.CreateInBoundsGEP(llvm_closure->getType(), //closure_lvtype, + llvm_closure, + index_v); + + llvm::Type * fnptr_lvtype + = type2llvm::function_td_to_llvm_fnptr_type(llvm_cx_, + apply->fn()->valuetype(), + true /*wrapper_flag*/); + + /* the thing we're going to call */ + lv_fnptr = ir_builder.CreateLoad(fnptr_lvtype, lv_fnptr_addr); +#endif + + std::array index_v = {{ 0 }}; + + //ir_builder.CreateExtractValue(Value *Agg, ArrayRef Idxs) + + lv_fnptr = ir_builder.CreateExtractValue(llvm_closure, + index_v, + "fnptr"); } llvm::Value * lv_fnenvptr = nullptr; { +#ifdef MAYBE_VERBOSE + llvm::Value * i0_slot + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, 0 /*value*/)); + llvm::Value * envptr_slot = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), llvm::APInt(32 /*bits*/, 1 /*value*/)); - std::array index_v = {{envptr_slot /*envptr slot = closure[1]*/}}; + std::array index_v + = {{i0_slot, + envptr_slot /*envptr slot = closure[1]*/}}; - lv_fnenvptr = ir_builder.CreateInBoundsGEP(closure_lvtype, - llvm_closure, - index_v); + llvm::Value * lv_fnenvptr_addr + = ir_builder.CreateInBoundsGEP(llvm_closure->getType(), //closure_lvtype, + llvm_closure, + index_v); + + llvm::Type * fnenvptr_lvtype + = type2llvm::env_api_llvm_ptr_type(llvm_cx_); + + lv_fnenvptr = ir_builder.CreateLoad(fnenvptr_lvtype, lv_fnenvptr_addr); +#endif + + std::array index_v = {{ 1 }}; + + lv_fnenvptr = ir_builder.CreateExtractValue(llvm_closure, + index_v, + "envptr"); } std::vector args; @@ -524,8 +559,13 @@ namespace xo { if (log) { /* TODO: print helper for llvm::Value* */ std::string llvm_value_str; - llvm::raw_string_ostream ss(llvm_value_str); - arg->print(ss); + + if (arg) { + llvm::raw_string_ostream ss(llvm_value_str); + arg->print(ss); + } else { + llvm_value_str = ""; + } log(xtag("i_arg", i), xtag("arg", llvm_value_str)); @@ -533,6 +573,14 @@ namespace xo { args.push_back(arg); ++i; + + if (!arg) { + cerr << "MachPipeline::codegen_apply: failed for i'th argument" + << xtag("i", i) + << endl; + + return nullptr; + } } /* if we have an intrinsic hint, @@ -571,9 +619,9 @@ namespace xo { } llvm::FunctionType * llvm_fn_type - = type2llvm::function_td_to_llvm_type(this->llvm_cx_, - ast_fn_td, - true /*wrapper_flag*/); + = type2llvm::function_td_to_lvtype(this->llvm_cx_, + ast_fn_td, + true /*wrapper_flag*/); return ir_builder.CreateCall(llvm_fn_type, lv_fnptr, @@ -623,13 +671,13 @@ namespace xo { * Note that this argument is not present in lambda, * so we need care. lambda->fn_arg(i) -> lvfn->arg [i+1] */ - llvm::FunctionType * llvm_fn_type - = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), - lambda->valuetype(), - true /*wrapper_flag*/); + llvm::FunctionType * fn_lvtype + = type2llvm::function_td_to_lvtype(llvm_cx_.borrow(), + lambda->valuetype(), + true /*wrapper_flag*/); /* create (initially empty) function */ - fn = llvm::Function::Create(llvm_fn_type, + fn = llvm::Function::Create(fn_lvtype, llvm::Function::ExternalLinkage, lambda->name(), llvm_module_.get()); @@ -660,7 +708,7 @@ namespace xo { llvm::Function * MachPipeline::codegen_lambda_defn(ref::brw lambda, - llvm::IRBuilder<> & ir_builder) + llvm::IRBuilder<> & /*ir_builder*/) { constexpr bool c_debug_flag = true; @@ -690,14 +738,19 @@ namespace xo { auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); - ir_builder.SetInsertPoint(block); + /* since we need to explictly set builder's insert point, + * make a new builder instead of disturbing the top-to-bottom flow of the + * called ir_builder + */ + llvm::IRBuilder<> tmp_ir_builder(llvm_cx_->llvm_cx_ref()); + tmp_ir_builder.SetInsertPoint(block); /** Actual parameters will need their own activation record. * Track its shape + setup/teardown here. **/ this->env_stack_.push(activation_record(lambda.get())); - bool ok_flag = this->env_stack_.top().bind_locals(llvm_cx_, llvm_fn, ir_builder); + bool ok_flag = this->env_stack_.top().bind_locals(llvm_cx_, llvm_fn, tmp_ir_builder); if (!ok_flag) { this->env_stack_.pop(); @@ -706,11 +759,11 @@ namespace xo { llvm::Value * retval = this->codegen(lambda->body(), envptr, - ir_builder); + tmp_ir_builder); if (retval) { /* completes the function.. */ - ir_builder.CreateRet(retval); + tmp_ir_builder.CreateRet(retval); /* validate! always validate! */ llvm::verifyFunction(*llvm_fn); @@ -742,7 +795,9 @@ namespace xo { this->env_stack_.pop(); - log && log("after pop, env stack size Z", xtag("Z", env_stack_.size())); + log && log("after pop, env stack size Z", + xtag("Z", env_stack_.size()), + xtag("llvm_fn", (void*)llvm_fn)); return llvm_fn; } /*codegen_lambda_defn*/ @@ -755,14 +810,21 @@ namespace xo { llvm::StructType * closure_lvtype = type2llvm::create_closureapi_lvtype(llvm_cx_.borrow(), lambda); - llvm::Function * lvfn = codegen_lambda_decl(lambda); + llvm::Function * lvfn = codegen_lambda_defn(lambda, ir_builder); + + if (!lvfn) { + cerr << "MachPipeline::codegen_lambda_closure: codegen lambda failed" + << endl; + return nullptr; + } llvm::Value * lv_closure = nullptr; - - lv_closure = ir_builder.CreateInsertValue(llvm::UndefValue::get(closure_lvtype), - lvfn, {0}, "lmfnptr" /*name*/); - lv_closure = ir_builder.CreateInsertValue(lv_closure, - envptr, {1}, "envptr" /*name*/); + { + lv_closure = ir_builder.CreateInsertValue(llvm::UndefValue::get(closure_lvtype), + lvfn, {0}); //, "lmfnptr" /*name*/); + lv_closure = ir_builder.CreateInsertValue(lv_closure, + envptr, {1}, "closure" /*name*/); + } return lv_closure; } /*codegen_lambda_closure*/ @@ -836,44 +898,45 @@ namespace xo { when_false_bb); /* populate when_true_bb */ - ir_builder.SetInsertPoint(when_true_bb); + llvm::IRBuilder<> tmp_ir_builder(llvm_cx_->llvm_cx_ref()); + tmp_ir_builder.SetInsertPoint(when_true_bb); llvm::Value * when_true_ir = this->codegen(expr->when_true(), envptr, - ir_builder); + tmp_ir_builder); if (!when_true_ir) return nullptr; /* at end of when-true sequence, jump to merge suffix */ - ir_builder.CreateBr(merge_bb); + tmp_ir_builder.CreateBr(merge_bb); /* note: codegen for expr->when_true() may have altered builder's "current block" */ - when_true_bb = ir_builder.GetInsertBlock(); + when_true_bb = tmp_ir_builder.GetInsertBlock(); /* populate when_false_bb */ parent_fn->insert(parent_fn->end(), when_false_bb); - ir_builder.SetInsertPoint(when_false_bb); + tmp_ir_builder.SetInsertPoint(when_false_bb); llvm::Value * when_false_ir = this->codegen(expr->when_false(), envptr, - ir_builder); + tmp_ir_builder); if (!when_false_ir) return nullptr; /* at end of when-false sequence, jump to merge suffix */ - ir_builder.CreateBr(merge_bb); + tmp_ir_builder.CreateBr(merge_bb); /* note: codegen for expr->when_false() may have altered builder's "current block" */ - when_false_bb = ir_builder.GetInsertBlock(); + when_false_bb = tmp_ir_builder.GetInsertBlock(); /* merged suffix sequence */ parent_fn->insert(parent_fn->end(), merge_bb); - ir_builder.SetInsertPoint(merge_bb); + tmp_ir_builder.SetInsertPoint(merge_bb); /** TODO: switch to getInt1Ty here **/ llvm::PHINode * phi_node - = ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), - 2 /*#of branches being merged (?)*/, - "iftmp"); + = tmp_ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), + 2 /*#of branches being merged (?)*/, + "iftmp"); phi_node->addIncoming(when_true_ir, when_true_bb); phi_node->addIncoming(when_false_ir, when_false_bb); diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index be1275ef..7b1228e6 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -33,7 +33,12 @@ namespace xo { } /*ctor*/ const runtime_binding_detail * - activation_record::lookup_var(const std::string & x) const { + activation_record::lookup_var(const std::string & x) const + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); auto ix = frame_.find(x); @@ -41,6 +46,10 @@ namespace xo { cerr << "activation_record::lookup_var: no binding for variable x" << xtag("x", x) << endl; + cerr << "frame:"; + for (const auto & ix : frame_) + cerr << xtag("var", ix.first) << xtag("->", ix.second) << endl; + return nullptr; } @@ -51,6 +60,14 @@ namespace xo { activation_record::alloc_var(const std::string & x, const runtime_binding_detail & binding) { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("var", x), + xtag("binding", binding)); + if (frame_.find(x) != frame_.end()) { cerr << "activation_record::alloc_var: variable x already present in frame" << xtag("x", x) @@ -76,10 +93,12 @@ namespace xo { constexpr bool c_debug_flag = true; using xo::scope; - scope log(XO_DEBUG(c_debug_flag), - xtag("llvm_fn", (void*)llvm_fn), - xtag("var_name", var_name), - xtag("var_type", var_type->short_name())); + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("llvm_fn", (void*)llvm_fn), + xtag("i_arg", i_arg), + xtag("var_name", var_name), + xtag("var_type", var_type->short_name())); llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, var_type); @@ -151,12 +170,16 @@ namespace xo { #endif llvm::IRBuilder<> & tmp_ir_builder) { + llvm::Value * i0_slot + = llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, 0)); + llvm::Value * i32_slot = llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(), llvm::APInt(32 /*bits*/, i_slot /*value*/)); - std::array index_v = { - {i32_slot /*environment slot #0*/}}; + std::array index_v = { + {i0_slot, i32_slot /*environment slot #0*/}}; llvm::Value * llvm_localenv_slot_ptr = tmp_ir_builder.CreateInBoundsGEP(localenv_llvm_type, @@ -216,7 +239,7 @@ namespace xo { tmp_ir_builder, i_arg, arg_name, - lambda_->fn_arg(i_arg)); + lambda_->fn_arg(i_arg-1)); if (!binding.llvm_addr_) return false; diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index 6a982b89..e5c07cac 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -58,20 +58,29 @@ namespace xo { * that represented by @p fn_td **/ llvm::FunctionType * - type2llvm::function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td, - bool wrapper_flag) + type2llvm::function_td_to_lvtype(xo::ref::brw llvm_cx, + TypeDescr fn_td, + bool wrapper_flag) { - int n_fn_arg = fn_td->n_fn_arg(); + constexpr bool c_debug_flag = false; + + scope log(XO_DEBUG(c_debug_flag)); + + int n_ast_fn_arg = fn_td->n_fn_arg(); + + if (log) { + log(xtag("fn_td", fn_td->short_name())); + log(xtag("n_ast_fn_arg", n_ast_fn_arg)); + } std::vector llvm_argtype_v; - llvm_argtype_v.reserve(n_fn_arg + (wrapper_flag ? 1 : 0)); + llvm_argtype_v.reserve(n_ast_fn_arg + (wrapper_flag ? 1 : 0)); if (wrapper_flag) llvm_argtype_v.push_back(env_api_llvm_ptr_type(llvm_cx)); /** check function args are all known **/ - for (int i = 0; i < n_fn_arg; ++i) { + for (int i = 0; i < n_ast_fn_arg; ++i) { TypeDescr arg_td = fn_td->fn_arg(i); llvm::Type * llvm_argtype = type2llvm::td_to_llvm_type(llvm_cx, arg_td); @@ -79,12 +88,26 @@ namespace xo { if (!llvm_argtype) return nullptr; + if (log) { + log(xtag("arg_td", arg_td->short_name())); + log(xtag("llvm_argtype", "...")); + llvm_argtype->dump(); + log("...done"); + } + llvm_argtype_v.push_back(llvm_argtype); } TypeDescr retval_td = fn_td->fn_retval(); llvm::Type * llvm_retval = type2llvm::td_to_llvm_type(llvm_cx, retval_td); + if (log) { + log(xtag("retval_td", retval_td->short_name())); + log(xtag("llvm_retval", "...")); + llvm_retval->dump(); + log("...done"); + } + if (!llvm_retval) return nullptr; @@ -96,17 +119,23 @@ namespace xo { llvm::PointerType * type2llvm::function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td, - bool wrapper_flag) + TypeDescr /*fn_td*/, + bool /*wrapper_flag*/) { - auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td, wrapper_flag); +#ifdef OBSOLETE + auto * llvm_fn_type = function_td_to_lvtype(llvm_cx, fn_td, wrapper_flag); +#endif /** like C: llvm IR doesn't support function-valued variables; * it does however support pointer-to-function-valued variables **/ + auto * llvm_ptr_type + = llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref()); +#ifdef OBSOLETE auto * llvm_ptr_type = llvm::PointerType::get(llvm_fn_type, 0 /*numbered address space*/); +#endif return llvm_ptr_type; } @@ -181,20 +210,26 @@ namespace xo { { assert(pointer_td->is_pointer()); +#ifdef OBSOLETE TypeDescr dest_td = pointer_td->fixed_child_td(0); llvm::Type * llvm_dest_type = type2llvm::td_to_llvm_type(llvm_cx, dest_td); llvm::PointerType * llvm_ptr_type = llvm::PointerType::getUnqual(llvm_dest_type); +#endif + + llvm::PointerType * llvm_ptr_type + = llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref()); return llvm_ptr_type; } /*pointer_td_llvm_type*/ llvm::PointerType * type2llvm::require_localenv_unwind_llvm_fnptr_type(xo::ref::brw llvm_cx, - llvm::PointerType * envptr_llvm_type) + llvm::PointerType * /*envptr_llvm_type*/) { +#ifdef OBSOLETE if (!envptr_llvm_type) envptr_llvm_type = env_api_llvm_ptr_type(llvm_cx); @@ -219,6 +254,9 @@ namespace xo { /* _env_api* (*)(_env_api*, i32) */ auto * unwind_llvm_fnptr_type = llvm::PointerType::getUnqual(unwind_llvm_type); +#endif + auto * unwind_llvm_fnptr_type + = llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref()); return unwind_llvm_fnptr_type; } /*require_localenv_unwind_llvm_fnptr_type*/ @@ -231,9 +269,13 @@ namespace xo { = llvm::StructType::get(llvm_cx->llvm_cx_ref(), "_env_api"); +#ifdef OBSOLETE /* _env_api[0]: pointer to a local environment */ llvm::PointerType * envptr_llvm_type = llvm::PointerType::getUnqual(env_llvm_type); +#endif + llvm::PointerType * envptr_llvm_type + = llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref()); /* _env_api[1]: unwwind/copy function */ llvm::PointerType * unwind_llvm_fnptr_type @@ -250,9 +292,11 @@ namespace xo { llvm::PointerType * type2llvm::env_api_llvm_ptr_type(xo::ref::brw llvm_cx) { +#ifdef OBSOLETE llvm::StructType * env_llvm_type = env_api_llvm_type(llvm_cx); - return llvm::PointerType::getUnqual(env_llvm_type); +#endif + return llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref()); } /*env_api_llvm_ptr_type*/ llvm::StructType * @@ -274,6 +318,10 @@ namespace xo { TypeDescr fn_td, const std::string & hint_name) { + constexpr bool c_debug_flag = false; + + scope log(XO_DEBUG(c_debug_flag)); + /* would be precisely correct to use create_localenv_llvm_type() * here. However judged not sufficiently helpful. * Would still @@ -283,9 +331,21 @@ namespace xo { llvm::PointerType * fn_lvtype = function_td_to_llvm_fnptr_type(llvm_cx, fn_td, true /*wrapper_flag*/); - llvm::StructType * env_lvtype = env_api_llvm_type(llvm_cx); + if (log) { + log(xtag("fn_lvtype", "...")); + fn_lvtype->dump(); + log("...done"); + } - std::vector member_lvtype_v = { fn_lvtype, env_lvtype }; + llvm::PointerType * envptr_lvtype = env_api_llvm_ptr_type(llvm_cx); + + if (log) { + log(xtag("env_lvtype", "...")); + envptr_lvtype->dump(); + log("...done"); + } + + std::vector member_lvtype_v = { fn_lvtype, envptr_lvtype }; llvm::StructType * closure_lvtype = llvm::StructType::get(llvm_cx->llvm_cx_ref(), member_lvtype_v); @@ -295,6 +355,12 @@ namespace xo { if (!hint_name.empty()) closure_lvtype->setName(llvm::StringRef(hint_name)); + if (log) { + log(xtag("closure_lvtype", "...")); + closure_lvtype->dump(); + log("...done"); + } + return closure_lvtype; } /*function_td_to_closureapi_lvtype*/ diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp index 880aabf1..ad14ac32 100644 --- a/utest/MachPipeline.test.cpp +++ b/utest/MachPipeline.test.cpp @@ -116,12 +116,19 @@ namespace xo { //auto rng = xo::rng::xoshiro256ss(seed); - scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipeline")); + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipeline.fptr")); //log && log("(A)", xtag("foo", foo)); - auto jit = MachPipeline::make(); for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + /** can't share jit across examples, + * until we fix treatment of primitives: + * now that we build a wrapper for each primitive, + * need some bookkeeping to avoid trying to build + * the same wrapper twice. + **/ + auto jit = MachPipeline::make(); + TestCase const & testcase = s_testcase_v[i_tc]; INFO(tostr(xtag("i_tc", i_tc))); From 97d095a0556e19cdc12eab6f5e5aa5920273e461 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 10 Jul 2024 16:09:35 -0400 Subject: [PATCH 1237/2524] xo-jit: ++ HOWTO --- HOWTO | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/HOWTO b/HOWTO index 6965105f..c3eed238 100644 --- a/HOWTO +++ b/HOWTO @@ -43,3 +43,30 @@ mp.machgen_current_module() fn=mp.lookup_fn('int (*)(int)', 'sq') fn(16) # -> 256 + +** to figure out what 'IR should look like' for something simple + + write some c++: + + #include + + struct env_type { + env_type * parent; + env_type * (*unwind)(env_type *, int); + }; + + double wrap_sqrt(env_type * env, double x) { + return ::sqrt(x); + } + + int main() { + wrap_sqrt(nullptr, 2.0); + } + + compile to emit IR + + $ clang -cc1 ex_cpp.cpp -emit-llvm + + inspect + + ex_cpp.ll From 0ee004cec685bca1bc5d267af473335d57900070 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 10 Jul 2024 16:09:52 -0400 Subject: [PATCH 1238/2524] xo-jit: + example/ex_cpp --- example/ex_cpp/ex_cpp.cpp | 40 ++++++++++++++ example/ex_cpp/ex_cpp.ll | 108 ++++++++++++++++++++++++++++++++++++++ example/ex_cpp/tmp.ll | 58 ++++++++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 example/ex_cpp/ex_cpp.cpp create mode 100644 example/ex_cpp/ex_cpp.ll create mode 100644 example/ex_cpp/tmp.ll diff --git a/example/ex_cpp/ex_cpp.cpp b/example/ex_cpp/ex_cpp.cpp new file mode 100644 index 00000000..65ce9934 --- /dev/null +++ b/example/ex_cpp/ex_cpp.cpp @@ -0,0 +1,40 @@ +struct env_type; + +struct closure_type { + double (*fnptr)(env_type * env, double x); + env_type * envptr; +}; + +double +sqrt(double x) { + return x/100; +} + +double +wrap_sqrt(env_type * env, double x) { + return ::sqrt(x); +} + +double twice(env_type * env, closure_type fnclosure, double x) { + double tmp1 = (*fnclosure.fnptr)(fnclosure.envptr, x); + double tmp2 = (*fnclosure.fnptr)(fnclosure.envptr, tmp1); + + return tmp2; +} + +closure_type make_some_closure() +{ + closure_type closure; + closure.fnptr = &wrap_sqrt; + closure.envptr = nullptr; + + return closure; +} + +int main() { + closure_type closure = make_some_closure(); + + double y = twice(nullptr, closure, 4.0); + + //std::cout << "y=" << y << std::endl; +} diff --git a/example/ex_cpp/ex_cpp.ll b/example/ex_cpp/ex_cpp.ll new file mode 100644 index 00000000..28434d62 --- /dev/null +++ b/example/ex_cpp/ex_cpp.ll @@ -0,0 +1,108 @@ +; ModuleID = 'ex_cpp.cpp' +source_filename = "ex_cpp.cpp" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +%struct.closure_type = type { ptr, ptr } + +; Function Attrs: mustprogress noinline nounwind optnone +define dso_local noundef double @_Z4sqrtd(double noundef %x) #0 { +entry: + %x.addr = alloca double, align 8 + store double %x, ptr %x.addr, align 8 + %0 = load double, ptr %x.addr, align 8 + %div = fdiv double %0, 1.000000e+02 + ret double %div +} + +; Function Attrs: mustprogress noinline nounwind optnone +define dso_local noundef double @_Z9wrap_sqrtP8env_typed(ptr noundef %env, double noundef %x) #0 { +entry: + %env.addr = alloca ptr, align 8 + %x.addr = alloca double, align 8 + store ptr %env, ptr %env.addr, align 8 + store double %x, ptr %x.addr, align 8 + %0 = load double, ptr %x.addr, align 8 + %call = call noundef double @_Z4sqrtd(double noundef %0) + ret double %call +} + +; Function Attrs: mustprogress noinline nounwind optnone +define dso_local noundef double @_Z5twiceP8env_type12closure_typed(ptr noundef %env, ptr %fnclosure.coerce0, ptr %fnclosure.coerce1, double noundef %x) #0 { +entry: + %fnclosure = alloca %struct.closure_type, align 8 + %env.addr = alloca ptr, align 8 + %x.addr = alloca double, align 8 + %tmp1 = alloca double, align 8 + %tmp2 = alloca double, align 8 + %0 = getelementptr inbounds { ptr, ptr }, ptr %fnclosure, i32 0, i32 0 + store ptr %fnclosure.coerce0, ptr %0, align 8 + %1 = getelementptr inbounds { ptr, ptr }, ptr %fnclosure, i32 0, i32 1 + store ptr %fnclosure.coerce1, ptr %1, align 8 + store ptr %env, ptr %env.addr, align 8 + store double %x, ptr %x.addr, align 8 + %fnptr = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 0 + %2 = load ptr, ptr %fnptr, align 8 + %envptr = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 1 + %3 = load ptr, ptr %envptr, align 8 + %4 = load double, ptr %x.addr, align 8 + %call = call noundef double %2(ptr noundef %3, double noundef %4) + store double %call, ptr %tmp1, align 8 + %fnptr1 = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 0 + %5 = load ptr, ptr %fnptr1, align 8 + %envptr2 = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 1 + %6 = load ptr, ptr %envptr2, align 8 + %7 = load double, ptr %tmp1, align 8 + %call3 = call noundef double %5(ptr noundef %6, double noundef %7) + store double %call3, ptr %tmp2, align 8 + %8 = load double, ptr %tmp2, align 8 + ret double %8 +} + +; Function Attrs: mustprogress noinline nounwind optnone +define dso_local { ptr, ptr } @_Z17make_some_closurev() #0 { +entry: + %retval = alloca %struct.closure_type, align 8 + %fnptr = getelementptr inbounds %struct.closure_type, ptr %retval, i32 0, i32 0 + store ptr @_Z9wrap_sqrtP8env_typed, ptr %fnptr, align 8 + %envptr = getelementptr inbounds %struct.closure_type, ptr %retval, i32 0, i32 1 + store ptr null, ptr %envptr, align 8 + %0 = load { ptr, ptr }, ptr %retval, align 8 + ret { ptr, ptr } %0 +} + +; Function Attrs: mustprogress noinline norecurse nounwind optnone +define dso_local noundef i32 @main() #1 { +entry: + %closure = alloca %struct.closure_type, align 8 + %y = alloca double, align 8 + %agg.tmp = alloca %struct.closure_type, align 8 + %call = call { ptr, ptr } @_Z17make_some_closurev() + %0 = getelementptr inbounds { ptr, ptr }, ptr %closure, i32 0, i32 0 + %1 = extractvalue { ptr, ptr } %call, 0 + store ptr %1, ptr %0, align 8 + %2 = getelementptr inbounds { ptr, ptr }, ptr %closure, i32 0, i32 1 + %3 = extractvalue { ptr, ptr } %call, 1 + store ptr %3, ptr %2, align 8 + call void @llvm.memcpy.p0.p0.i64(ptr align 8 %agg.tmp, ptr align 8 %closure, i64 16, i1 false) + %4 = getelementptr inbounds { ptr, ptr }, ptr %agg.tmp, i32 0, i32 0 + %5 = load ptr, ptr %4, align 8 + %6 = getelementptr inbounds { ptr, ptr }, ptr %agg.tmp, i32 0, i32 1 + %7 = load ptr, ptr %6, align 8 + %call1 = call noundef double @_Z5twiceP8env_type12closure_typed(ptr noundef null, ptr %5, ptr %7, double noundef 4.000000e+00) + store double %call1, ptr %y, align 8 + ret i32 0 +} + +; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) +declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #2 + +attributes #0 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" } +attributes #1 = { mustprogress noinline norecurse nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" } +attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 18.1.5"} diff --git a/example/ex_cpp/tmp.ll b/example/ex_cpp/tmp.ll new file mode 100644 index 00000000..b8b8993a --- /dev/null +++ b/example/ex_cpp/tmp.ll @@ -0,0 +1,58 @@ +define double @root4(ptr %.env, double %x) { +entry: + %x1 = alloca double, align 8 + store double %x, ptr %x1, align 8 + %x2 = load double, ptr %x1, align 8 + %calltmp = call double @w.sqrt(ptr null, double %x2) + %calltmp3 = call double @w.sqrt(ptr null, double %calltmp) + ret double %calltmp3 +} + +; ---------------------------------------------------------------- + +define double @twice(ptr %.env, { ptr, ptr } %f, double %x) { +entry: + %f1 = alloca { ptr, ptr }, align 8 + store { ptr, ptr } %f, ptr %f1, align 8 + %x2 = alloca double, align 8 + store double %x, ptr %x2, align 8 + %f3 = load { ptr, ptr }, ptr %f1, align 8 + %fnptr = extractvalue { ptr, ptr } %f3, 0 + %envptr = extractvalue { ptr, ptr } %f3, 1 + %f4 = load { ptr, ptr }, ptr %f1, align 8 + %fnptr5 = extractvalue { ptr, ptr } %f4, 0 + %envptr6 = extractvalue { ptr, ptr } %f4, 1 + %x7 = load double, ptr %x2, align 8 + %calltmp = call double %fnptr5(ptr %envptr6, double %x7) + %calltmp8 = call double %fnptr(ptr %envptr, double %calltmp) + ret double %calltmp8 +} + + +define double @twice(ptr %.env, { ptr, ptr } %f, double %x) { +entry: + %f1 = alloca { ptr, ptr }, align 8 + %f.elt = extractvalue { ptr, ptr } %f, 0 + store ptr %f.elt, ptr %f1, align 8 + %f1.repack9 = getelementptr inbounds { ptr, ptr }, ptr %f1, i64 0, i32 1 + %f.elt10 = extractvalue { ptr, ptr } %f, 1 + store ptr %f.elt10, ptr %f1.repack9, align 8 + %calltmp = call double %f.elt(ptr %f.elt10, double %x) + %calltmp8 = call double %f.elt(ptr %f.elt10, double %calltmp) + ret double %calltmp8 +} + + +;; ---------------------------------------------------------------- + +define double @root_2x(ptr %.env, double %x2) { +entry: + %x21 = alloca double, align 8 + store double %x2, ptr %x21, align 8 + %envptr = insertvalue { ptr, ptr } { ptr @twice, ptr undef }, ptr %.env, 1 + %fnptr = extractvalue { ptr, ptr } %envptr, 0 + %envptr2 = extractvalue { ptr, ptr } %envptr, 1 + %x23 = load double, ptr %x21, align 8 + %calltmp = call double %fnptr(ptr %envptr2, { ptr, ptr } { ptr @w.sqrt, ptr null }, double %x23) + ret double %calltmp +} From 3fa4029bc222f70c5c05d64c0fe2303a4de26112 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 10 Jul 2024 16:11:20 -0400 Subject: [PATCH 1239/2524] xo-jit: + example/ex_cpp/README --- example/ex_cpp/README | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 example/ex_cpp/README diff --git a/example/ex_cpp/README b/example/ex_cpp/README new file mode 100644 index 00000000..b67ad2e8 --- /dev/null +++ b/example/ex_cpp/README @@ -0,0 +1,6 @@ +Not including this in build for now. +Instead, use to manually generate .ll output: + +$ clang -cc1 ex_cpp.cpp -emit-llvm + +and inspect ex_cpp.ll From 9dc37e84e67f00e65ab366fe305e8c5d9b516ea0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 22 Jul 2024 12:30:46 +1000 Subject: [PATCH 1240/2524] xo-tokenizer: build + utest + reasonable implementation --- CMakeLists.txt | 27 ++ cmake/tokenizerConfig.cmake.in | 8 + cmake/xo-bootstrap-macros.cmake | 35 ++ include/xo/tokenizer/buffer.hpp | 324 +++++++++++++++ include/xo/tokenizer/span.hpp | 141 +++++++ include/xo/tokenizer/token.hpp | 334 +++++++++++++++ include/xo/tokenizer/tokenizer.hpp | 625 +++++++++++++++++++++++++++++ include/xo/tokenizer/tokentype.hpp | 142 +++++++ src/tokenizer/CMakeLists.txt | 14 + src/tokenizer/token.cpp | 9 + src/tokenizer/tokentype.cpp | 56 +++ utest/CMakeLists.txt | 13 + utest/token.test.cpp | 260 ++++++++++++ utest/tokenizer.test.cpp | 160 ++++++++ utest/tokenizer_utest_main.cpp | 6 + 15 files changed, 2154 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/tokenizerConfig.cmake.in create mode 100644 cmake/xo-bootstrap-macros.cmake create mode 100644 include/xo/tokenizer/buffer.hpp create mode 100644 include/xo/tokenizer/span.hpp create mode 100644 include/xo/tokenizer/token.hpp create mode 100644 include/xo/tokenizer/tokenizer.hpp create mode 100644 include/xo/tokenizer/tokentype.hpp create mode 100644 src/tokenizer/CMakeLists.txt create mode 100644 src/tokenizer/token.cpp create mode 100644 src/tokenizer/tokentype.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/token.test.cpp create mode 100644 utest/tokenizer.test.cpp create mode 100644 utest/tokenizer_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..d6500a69 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,27 @@ +# xo-tokenizer/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(tokenizer VERSION 0.1) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings + +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- + +add_subdirectory(src/tokenizer) +add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/cmake/tokenizerConfig.cmake.in b/cmake/tokenizerConfig.cmake.in new file mode 100644 index 00000000..f13d9e2b --- /dev/null +++ b/cmake/tokenizerConfig.cmake.in @@ -0,0 +1,8 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +#find_dependency(refcnt) +find_dependency(indentlog) +#find_dependency(subsys) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/include/xo/tokenizer/buffer.hpp b/include/xo/tokenizer/buffer.hpp new file mode 100644 index 00000000..eb206eac --- /dev/null +++ b/include/xo/tokenizer/buffer.hpp @@ -0,0 +1,324 @@ +/** @file buffer.hpp **/ + +#pragma once + +#include "span.hpp" +#include +#include +#include +#include + +namespace xo { + namespace tok { + /** + * @class buffer buffer.hpp + * + * @brief Container for a (possibly owned) FIFO queue of chars + * + * @tparam CharT. buffer element type. + * + * @code + * .buf + * + * +------------------------------------------+ + * | | ... | | X| ... | X| | ... | | + * +------------------------------------------+ + * ^ ^ ^ ^ + * 0 .lo .hi .buf_z + * + * <-contents-><----avail-----> + * @endcode + * + * Buffer does not support wrapped content: + * content that has not been consumed always occupies contiguous memory. + * + * Example: + * @code + * // 1. + * buffer buf(64*1024); + * buf.empty() -> true + * buf.buf_z() -> 65536 + * buf.lo_pos() -> 0 + * buf.hi_pos() -> 65536 + * buf.contents() -> empty span + * buf.avail() -> span entire buffer memory + * + * // write to (a prefix of) buf.avail() + * ::strncpy(buf.buf(), "hello, world\n", 13); + * buf.produce(span_type(buf.buf(), buf.buf() + 13)); + * + * buf.lo_pos() -> 0 + * buf.hi_pos() -> 13 + * buf.contents() -> "hello, world\n"; + * + * + * // examine stored content (does not change buffer state) + * auto span = buf.contents(); + * cerr << string_view(span.lo(), span.hi()); // "hello, world\n" + * + * // consume (a prefix of) stored content + * buf.consume(span.prefix(7); + * + * buf.lo_pos() -> 7 + * buf.hi_pos() -> 13 + * buf.contents() -> "world\n" + * + * // consuming all remain content resets to original state + * buf.consume(buf.contents()); + * + * buf.empty() -> true + * buf.hi_pos() -> 0 // not 13! + * + * // 2. + * buffer buf; + * buf.empty() -> true + * buf.buf_z() -> 0 + * buf.lo_pos() -> 0 + * buf.hi_pos() -> 0 + * buf.contents() -> empty span + * buf.avail() -> empty span + * + * // allocate memory separately from ctor + * buf.alloc(64*1024); + * @endcode + **/ + template + class buffer { + public: + /** @brief typealias for span of CharT **/ + using span_type = span; + /** @brief typealias for buffer size (counts CharT's, not bytes) **/ + using size_type = std::uint64_t; + + public: + /** @brief create empty buffer. + + Does not allocate any storage; @see alloc + **/ + buffer() = default; + /** @brief create empty buffer, and possibly allocate storage. + + @param buf_z Buffer size. allocate storage (owned by this buffer) if >0. + @param align_z Align to this value, e.g. 8 to align storage on an 8-byte boundary + **/ + buffer(size_type buf_z, size_type align_z = sizeof(char)) + : is_owner_{true}, + buf_{buf_z ? (new (std::align_val_t(align_z)) CharT [buf_z]) : nullptr}, + buf_z_{buf_z}, + lo_pos_{0}, + hi_pos_{0} + {} + /** @brief buffer is not copyable **/ + buffer(buffer const & x) = delete; + /** @brief destructor. Release storage if owned **/ + ~buffer() { this->reset(); } + + /** @name Access methods **/ + ///@{ + + /** @brief start of buffer memory **/ + CharT * buf() const { return buf_; } + /** @brief buffer size (number of characters) **/ + size_type buf_z() const { return buf_z_; } + /** @brief current start position within buffer **/ + size_type lo_pos() const { return lo_pos_; } + /** @brief current end position within buffer **/ + size_type hi_pos() const { return hi_pos_; } + + ///@} + + /** @brief readonly access to a single buffer element. + + Relative to start of buffer (ignores current consume position) + **/ + CharT const & operator[](size_type i) const { return buf_[i]; } + + /** @brief return span for current buffer contents **/ + span_type contents() const { return span_type(buf_ + lo_pos_, buf_ + hi_pos_); } + /** @brief returns span for writable buffer contents (unused prefix following produce position **/ + span_type avail() const { return span_type(buf_ + hi_pos_, buf_ + buf_z_); } + + /** @brief @c true iff buffer is empty **/ + bool empty() const { return lo_pos_ == hi_pos_; } + + + /** + @brief update buffer produce position, after (independently) writing contents of span to it + + @pre left endpoint of @p span equals buffer produce position (@c .hi_pos) + @pre right endpoint of @p span within bounds of buffer memory range + @post right endpoint of @p span equals buffer produce position. + **/ + void produce(span_type const & span) { + assert(span.lo() == buf_ + hi_pos_); + + hi_pos_ += span.size(); + } + + /** + @brief update buffer consume position, when done with contents of span + + @pre left endpoint of @p span equals buffer consume position (@c .lo_pos) + @pre right endpoint of @p span within bounds of buffer memory range + @post Either + buffer is empty, with @c .lo_pos = @c .hi_pos = @c 0. + buffer is non-empty, right endpoint of @p span equals new buffer consume position. + **/ + void consume(span_type const & span) { + if (span.size()) { + assert(span.lo() == buf_ + lo_pos_); + + lo_pos_ += span.size(); + } else { + /* since .consume() that arrives at empty contents also resets .lo_pos .hi_pos, + * we don't want to blow up when called with an empty span -- argument + * may represent some pre-reset location in buffer + */ + } + + if (lo_pos_ == hi_pos_) { + lo_pos_ = 0; + hi_pos_ = 0; + } + } + + /** + @brief allocate buffer with desired amount of memory + + @param buf_z desired buffer size + @param align_z alignment; buffer memory will be aligned on this byte-boundary. + **/ + void alloc(size_type buf_z, size_type align_z = sizeof(char)) { + /* properly reset (+ discard) any existing state */ + this->reset(); + + is_owner_ = true; + if (buf_z) + buf_ = new (std::align_val_t(align_z)) CharT [buf_z]; + buf_z_ = buf_z; + lo_pos_ = 0; + hi_pos_ = 0; + } + + /** + @brief attach buffer to (unowned) range of @p buf_z bytes starting at @p buf[0] + + Buffer is not responsible for managing storage. + + @post + 1. buffer is empty + @post + 2. buffer read position = buffer write position = 0 + **/ + void setbuf(CharT * buf, size_type buf_z) { + /* properly reset (+ discard) any existing state */ + this->reset(); + + is_owner_ = false; + lo_pos_ = 0; + hi_pos_ = 0; + buf_ = buf; + buf_z_ = buf_z; + } + + /** + @brief revert buffer to empty state and possibly zero it + + @param zero_buffer_flag Zero buffer contents iff this is true + + @post + 1. buffer is empty + @post + 2. buffer read position = buffer write position = 0 + **/ + void clear2empty(bool zero_buffer_flag) { + if (buf_ && zero_buffer_flag) + explicit_bzero(buf_, buf_z_ * sizeof(CharT)); + + lo_pos_ = 0; + hi_pos_ = 0; + } + + /** + @brief swap representation with another buffer instance. + **/ + void swap (buffer & x) { + std::swap(is_owner_, x.is_owner_); + std::swap(buf_, x.buf_); + std::swap(buf_z_, x.buf_z_); + std::swap(lo_pos_, x.lo_pos_); + std::swap(hi_pos_, x.hi_pos_); + } + + /** + @brief reset buffer to an empty state and recover owned storage + **/ + void reset() { + if (is_owner_ && buf_) + delete [] buf_; + + is_owner_ = false; + buf_ = nullptr; + buf_z_ = 0; + lo_pos_ = 0; + hi_pos_ = 0; + } + + /** + @brief move-assignment operator. + @param x right-hand-side to move from. + + @post + @p x is in a valid, empty, + **/ + buffer & operator= (buffer && x) { + is_owner_ = x.is_owner_; + buf_ = x.buf_; + buf_z_ = x.buf_z_; + lo_pos_ = x.lo_pos_; + hi_pos_ = x.hi_pos_; + + x.is_owner_ = false; + x.lo_pos_ = 0; + x.hi_pos_ = 0; + x.buf_ = nullptr; + x.buf_z_ = 0; + + return *this; + } + + /** @brief buffer is not assignable */ + buffer & operator= (buffer & x) = delete; + + private: + /** @brief true iff buffer is responsible for freeing storage at @c buf_ **/ + bool is_owner_ = false; + /** @brief buffer contents. buffer memory comprises @c buf_[0] to @c buf_[buf_z_] **/ + CharT * buf_ = nullptr; + /** @brief buffer size (in units of CharT) **/ + size_type buf_z_ = 0; + + /** @brief buffer read (consume) position + + @invariant + 0 <= lo_pos_ <= hi_pos_ < buf_z_ + **/ + size_type lo_pos_ = 0; + /** @brief buffer write (produce) position + + @invariant + 0 <= hi_pos_ < hi_pos_ < buf_z_ + **/ + size_type hi_pos_ = 0; + }; + + /** @brief Overload for @c swap, so that @c buffer swappable **/ + template + inline void + swap(buffer & lhs, buffer & rhs) { + lhs.swap(rhs); + } + } /*namespace tok*/ +} /*namespace xo*/ + +/* end buffer.hpp */ diff --git a/include/xo/tokenizer/span.hpp b/include/xo/tokenizer/span.hpp new file mode 100644 index 00000000..2f847f6a --- /dev/null +++ b/include/xo/tokenizer/span.hpp @@ -0,0 +1,141 @@ +/** @file span.hpp **/ + +#pragma once + +#include +#include +#include + +namespace xo { + namespace tok { + /** @class span compression/span.hpp + * + * @brief Represents a contiguous memory range, without ownership. + * + * @tparam CharT type for elements referred to by this span. + **/ + template + class span { + public: + /** @brief typealias for span size (in units of CharT) **/ + using size_type = std::uint64_t; + + public: + /** @brief create span for the contiguous memory range [@p lo, @p hi) **/ + span(CharT * lo, CharT * hi) : lo_{lo}, hi_{hi} {} + + ///@{ + + /** @name getters **/ + + CharT * lo() const { return lo_; } /* get member span::lo_ */ + CharT * hi() const { return hi_; } /* get member span::hi_ */ + + ///@} + + /** @brief create new span over supplied type, + * with identical (possibly misaligned) endpoints. + * + * @warning + * 1. New span uses exactly the same memory addresses. + * Endpoint pointers may not be aligned. + * 2. Implementation assumes code compiled with + * @code -fno-strict-aliasing @endcode enabled. + * + * @tparam OtherT element type for new span + **/ + template + span + cast() const { return span(reinterpret_cast(lo_), + reinterpret_cast(hi_)); } + + /** @brief create span including the first @p z members of this span. **/ + span prefix(size_type z) const { return span(lo_, lo_ + z); } + + /** @brief create span representing prefix up to (but not including) @p *p + **/ + span prefix(CharT * p) const { + if (p <= hi_) + return span(lo_, p); + else + return span(lo_, hi_); + } + + /** @brief create span with first @p z members of this span removed **/ + span after_prefix(size_type z) const { + if (z > hi_ - lo_) + z = hi_ - lo_; + + return span(lo_ + z, hi_); + } + + /** @brief create span with @p prefix of this span removed **/ + span after_prefix(const span & prefix) const { + assert(prefix.lo() == lo_); + if (prefix.lo() != lo_) { + throw std::runtime_error + ("after_prefix: expected prefix of this span"); + } + + return after_prefix(prefix.size()); + } + + /** @brief create span starting with position p **/ + span suffix_from(CharT * p) const { + if ((lo_ <= p) && (p <= hi_)) + return span(p, hi_); + else + return span(hi_, hi_); + } + + /** @brief true iff this span is empty (comprises 0 elements). **/ + bool empty() const { return lo_ == hi_; } + /** @brief report the number of elements (of type CharT) in this span. **/ + size_type size() const { return hi_ - lo_; } + + /** print representation for this span on stream @p os **/ + void print(std::ostream & os) const { + os << ""; + } + + private: + ///@{ + + /** @brief start of span + Span comprises memory address between @p lo (inclusive) and @p hi (exclusive) + **/ + CharT * lo_ = nullptr; + /** @brief end of span + Span comprises memory address between @p lo (inclusive) and @p hi (exclusive) + **/ + CharT * hi_ = nullptr; + + ///@} + }; /*span*/ + + template + inline bool + operator==(const span & lhs, const span & rhs) { + return ((lhs.lo() == rhs.lo()) + && (lhs.hi() == rhs.hi())); + } + + template + inline bool + operator!=(const span & lhs, const span & rhs) { + return ((lhs.lo() != rhs.lo()) + || (lhs.hi() != rhs.hi())); + } + + template + inline std::ostream & + operator<<(std::ostream & os, + const span & x) { + x.print(os); + return os; + } + } /*namespace tok*/ +} /*namespace xo*/ diff --git a/include/xo/tokenizer/token.hpp b/include/xo/tokenizer/token.hpp new file mode 100644 index 00000000..643a5143 --- /dev/null +++ b/include/xo/tokenizer/token.hpp @@ -0,0 +1,334 @@ +/* file token.hpp + * + * author: Roland Conybeare, Jul 2024 + */ + +#pragma once + +#include "tokentype.hpp" +#include "xo/indentlog/print/tag.hpp" +#include +#include +#include + +namespace xo { + namespace tok { + namespace detail { + /* compute a * b^p, p >= 0 */ + constexpr double + pow_aux(double a, double b, int p) { + while (p > 0) { + if (p % 2 == 1) { + /* a * b^p = a * b^(2q + 1) = a.b * 10^(2q) */ + a *= b; + p -= 1; + } else { + /* a * b^p = a * b^(2q) = a * (b^2)^q */ + b = b * b; + p /= 2; + } + } + + /* a * b^0 = a */ + return a; + } + + constexpr double + pow10(int p) { + if (p >= 0) + return pow_aux(1.0, 10.0, p); + else + return 1.0 / pow_aux(1.0, 10.0, -p); + } + } + + template + class token { + public: + token() = default; + token(tokentype tk_type, const std::string & text = "") + : tk_type_{tk_type}, text_{text} {} + + static token invalid() { return token(); } + static token i64_token(const std::string & txt) { + return token(tokentype::tk_i64, txt); + } + static token f64_token(const std::string & txt) { + return token(tokentype::tk_f64, txt); + } + static token string_token(const std::string & txt) { + return token(tokentype::tk_string, txt); + } + static token symbol_token(const std::string & txt) { + return token(tokentype::tk_symbol, txt); + } + static token leftangle() { return token(tokentype::tk_leftangle); } + static token rightangle() { return token(tokentype::tk_rightangle); } + static token leftparen() { return token(tokentype::tk_leftparen); } + static token rightparen() { return token(tokentype::tk_rightparen); } + static token leftbracket() { return token(tokentype::tk_leftbracket); } + static token rightbracket() { return token(tokentype::tk_rightbracket); } + static token leftbrace() { return token(tokentype::tk_leftbrace); } + static token rightbrace() { return token(tokentype::tk_rightbrace); } + static token dot() { return token(tokentype::tk_dot); } + static token comma() { return token(tokentype::tk_comma); } + static token colon() { return token(tokentype::tk_colon); } + static token doublecolon() { return token(tokentype::tk_doublecolon); } + static token semicolon() { return token(tokentype::tk_semicolon); } + static token singleassign() { return token(tokentype::tk_singleassign); } + static token assign() { return token(tokentype::tk_assign); } + static token yields() { return token(tokentype::tk_yields); } + + static token type() { return token(tokentype::tk_type); } + static token def() { return token(tokentype::tk_def); } + static token lambda() { return token(tokentype::tk_lambda); } + static token if_token() { return token(tokentype::tk_if); } + static token let() { return token(tokentype::tk_let); } + static token in() { return token(tokentype::tk_in); } + + tokentype tk_type() const { return tk_type_; } + const std::string & text() const { return text_; } + + bool is_valid() const { return tk_type_ != tokentype::tk_invalid; } + bool is_invalid() const { return tk_type_ == tokentype::tk_invalid; } + + /** expect input matching + * [+|-][0-9][0-9]* + **/ + std::int64_t i64_value() const; + /** expect input matching + * [+|-][0-9]*[.][0-9]*[e|E][+|-][0-9]* + **/ + double f64_value() const; + + private: + /** category for this token **/ + tokentype tk_type_ = tokentype::tk_invalid; + + /** characters comprising this token. + * only provided for certain token types: + * + * tk_i64 + * tk_f64 + * tk_string + * tk_symbol + **/ + std::string text_; + }; /*token*/ + + template + std::int64_t + token::i64_value() const { + if (tk_type_ != tokentype::tk_i64) { + throw (std::runtime_error + (tostr("token::i64_value", + ": token with type tk found where tk_i64 expected", + xtag("tk", tk_type_)))); + } + + if (text_.empty()) { + throw (std::runtime_error + (tostr("token::i64_value", + ": unexpected empty input string for tk_i64 token"))); + } + + int sign = 1; + int value = 0; + { + auto ix = text_.begin(); + auto end_ix = text_.end(); + + CharT ch = *ix; + + if (ch == '+') { + ++ix; + } else if (ch == '-') { + sign = -1; + ++ix; + } + + if (ix == end_ix) { + throw (std::runtime_error + (tostr("token::i64_value", + ": input text found where at least one digit expected", + xtag("text", text_)))); + } + + for (; ix != end_ix; ++ix) { + CharT ch = *ix; + + if ((ch >= '0') && (ch <= '9')) { + value *= 10; + value += (ch - '0'); + } else { + throw (std::runtime_error + (tostr("token::i64_value", + ": unexpected char ch in integer token", + xtag("ch", ch)))); + } + } + } + + return sign * value; + } /*i64_value*/ + + template + double + token::f64_value() const { + if (tk_type_ != tokentype::tk_f64) { + throw (std::runtime_error + (tostr("token::f64_value", + ": token with type tk found where tk_f64 expected", + xtag("tk", tk_type_)))); + } + + if (text_.empty()) { + throw (std::runtime_error + (tostr("token::f64_value", + ": unexpected empty input string for tk_f64 token"))); + } + + int sign = 1; + /* integer representing denormalized unsigned mantissa + * (mantissa scaled by smallest power of 10 sufficient to make + * it an integer) + */ + std::int64_t mantissa = 0; + /* counts #of digits to the right of decimal point '.' */ + int rh_digits = 0; + /* sign of exponent */ + int exp_sign = 1; + /* value of exponenct = integer to the right of 'e' or 'E' */ + int exponent = 0; + + /* floating-point value will represent + * sign * mantissa * 10^(sign*exponent - rh_digits) + */ + { + auto ix = text_.begin(); + auto end_ix = text_.end(); + + CharT ch = *ix; + + if (ch == '+') { + ++ix; + } else if (ch == '-') { + sign = -1; + ++ix; + } + + if (ix == end_ix) { + throw (std::runtime_error + (tostr("token::f64_value", + ": input text found where at least one digit expected", + xtag("text", text_)))); + } + + /* true iff decimal point '.' present in mantissa */ + bool have_decimal_point = false; + /* true iff exponent prefix 'e' or 'E' present */ + //bool have_exponent = false; + /* counts number of digits in mantissa + * (both before and after, but not including, any decimal point + */ + int m_digits = 0; + /* digits to the left of decimal point */ + int lh_digits = 0; + + /* loop over mantissa digits */ + for (; ix != end_ix; ++ix) { + CharT ch = *ix; + + if (ch == '.') { + if (have_decimal_point) { + throw (std::runtime_error + (tostr("token::f64_value", + ": input text found where at most one decimal point expected", + xtag("text", text_)))); + } + + have_decimal_point = true; + lh_digits = m_digits; + } else if ((ch >= '0') && (ch <= '9')) { + mantissa *= 10; + mantissa += (ch - '0'); + ++m_digits; + } else if (ch == 'e' || ch == 'E') { + //have_exponent = true; + break; // done with mantissa + } else { + throw (std::runtime_error + (tostr("token::i64_value", + ": unexpected char ch in integer token", + xtag("ch", ch)))); + } + } + + if (have_decimal_point) + rh_digits = m_digits - lh_digits; + + if (ix != end_ix) { + /* continue to read exponent */ + + /* skip e|E */ + ++ix; + + if (ix == end_ix) { + throw (std::runtime_error + (tostr("token::f64_value", + ": on input text, expect at least one digit following exponent marker e|E", + xtag("text", text_)))); + } + + CharT ch = *ix; + + if (ch == '+') { + ++ix; /*skip*/ + } else if (ch == '-') { + exp_sign = -1; + ++ix; + } + + for (; ix != end_ix; ++ix) { + CharT ch = *ix; + + if ((ch >= '0') && (ch <= '9')) { + exponent *= 10; + exponent += (ch - '0'); + } else { + throw (std::runtime_error + (tostr("token::f64_value", + "; on input text, expect only digits following" + " (possibly signed) exponenct marker", + xtag("text", text_)))); + } + } + } + } + + /* floating-point value will represent + * sign * mantissa * 10^(sign*exponent - rh_digits) + */ + + double mantissa_f64 = sign * mantissa; + +#ifdef OBSOLETE_DEBUG + std::cerr << xtag("text", text_) + << xtag("rh_digits", rh_digits) + << xtag("mantissa_f64", mantissa_f64) + << xtag("exp_sign", exp_sign) + << xtag("exponent", exponent) + << std::endl; +#endif + + double retval = (mantissa_f64 + * detail::pow10((exp_sign * exponent) + - rh_digits)); + + return retval; + } /*f64_value*/ + } /*Namespace tok*/ +} /*namespace xo*/ + + +/* end token.hpp */ diff --git a/include/xo/tokenizer/tokenizer.hpp b/include/xo/tokenizer/tokenizer.hpp new file mode 100644 index 00000000..bc5164f7 --- /dev/null +++ b/include/xo/tokenizer/tokenizer.hpp @@ -0,0 +1,625 @@ +/* file tokenizer.hpp + * + * author: Roland Conybeare, Jul 2024 + */ + +#pragma once + +#include "token.hpp" +#include "span.hpp" +#include "xo/indentlog/scope.hpp" +#include + +namespace xo { + namespace tok { + /** + * Use: + * @code + * using tokenizer_type = tokenizer; + * using span_type = tokenizer_type::span_type; + * + * tokenizer_type tkz; + * span_type input = ...; + * + * while !input.empty() { + * auto res = tkz.assemble_scan(input); + * const auto & tk = res.first; + * + * // do something with tk if tk.is_valid() + * + * input = input.after_prefix(res.second); + * } + * + * if endofinput { + * auto tk = tzk.notify_eof() + * + * // do something with tk if tk.is_valid() + * } + * + * // expect !tkz.has_prefix() + * + * @endcode + **/ + template + class tokenizer { + public: + using token_type = token; + using span_type = span; + using scan_result = std::pair; + + public: + tokenizer() = default; + + /** identifies whitespace chars. + * These are chars that do not belong to any token. + * They are not permitted to appear within + * a symbol or string token. + * Appearance of a whitespace char forces completion of + * preceding token. + **/ + bool is_whitespace(CharT ch) const; + + /** identifies punctuation chars. + * These are chars that are not permitted to appear within + * a string/symbol token. Instead they force completion of + * a preceding token, and start a new token with themselves + **/ + bool is_punctuation(CharT ch) const; + + /** true if tokenizer contains stored prefix of + * possibly-incomplete token + **/ + bool has_prefix() const { !prefix_.empty(); } + + /** assemble token from text @p token_text + **/ + token_type assemble_token(const span_type & token_text) const; + + /** scan for next input token, given @p input **/ + scan_result scan(const span_type & input); + + /** notify end of input, resolve any stored input **/ + token_type notify_eof(); + + private: + /** Accumulate partial token here. + * This will happen if input sent to @ref tokenizer::scan + * ends without a determinate token boundary. + **/ + std::string prefix_; + }; /*tokenizer*/ + + template + bool + tokenizer::is_whitespace(CharT ch) const { + switch(ch) { + case ' ': return true; + case '\t': return true; + case '\n': return true; + case '\r': return true; + } + + return false; + } + + template + bool + tokenizer::is_punctuation(CharT ch) const { + switch(ch) { + case '<': + return true; + case '>': + return true; + case '(': + return true; + case ')': + return true; + case '[': + return true; + case ']': + return true; + case '{': + return true; + case '}': + return true; + case ',': + return true; + case ';': + return true; + case ':': + return true; + case '=': + return true; + case '-': + /* can't be punctuation -- can appear inside f64 token */ + return false; + case '+': + /* can't be punctuation -- can appear inside f64 token */ + return false; + case '.': + /* can't be punctuation -- can appear inside f64 token */ + return false; + } + + return false; + } + + template + auto + tokenizer::assemble_token(const span_type & token_text) const -> token_type + { + constexpr bool c_debug_flag = true; + + /* literal|pretty|streamlined */ + log_config::style = function_style::streamlined; + + scope log(XO_DEBUG(c_debug_flag)); + log && log(xtag("token_text", token_text)); + + tokentype tk_type = tokentype::tk_invalid; + std::string tk_text; + + const CharT * tk_start = token_text.lo(); + const CharT * tk_end = token_text.hi(); + + const CharT * ix = tk_start; + + /* switch here applies to the first character in a token */ + switch (*ix) { + case '-': + case '+': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + /* examples of valid floating-point numbers: + * .0 + * 1e0 + * 1e + * 0. + * +1e0 + * -1e0 + * +1E+2 + * -1E+2 + * -0.123e-10 + * non-examples: + * . + * - + * + + * e0 + * .e0 + * -.e-0 + * +.e+0 + * + * in particular: to be recognized as a number, + * must contain at least one digit + */ + + log && log("possible number-token"); + + /* true if initial sign -/+ encountered */ + bool sign_flag = false; + /* true if '.' encountered */ + bool period_flag = false; + /* true if 'e' | 'E' encountered. + */ + bool exponent_flag = false; + /* true when sign '-' | '+' precedes exponenct digits */ + bool exponent_sign_flag = false; + /* true when at least one digit follows exponent marker */ + bool exponent_digit_flag = false; + /* true if at least one digit encountered */ + bool number_flag = false; + + /* token will be one of: {i64, f64, dot}: */ + for(; ix != token_text.hi(); ++ix) { + if((*ix == '-') || (*ix == '+')) { + /* sign allowed: + * 1. before period and before first digit + * 2. after exponent + */ + if (!period_flag && !number_flag && !sign_flag) { + sign_flag = true; + } else if (exponent_flag && !exponent_digit_flag) { + exponent_sign_flag = true; + } else { + throw std::runtime_error + (tostr("tokenizer::assemble_token", + ": improperly placed sign indicator", + xtag("pos", ix - tk_start), + xtag("char", *ix))); + } + } else if(*ix == '.') { + if (period_flag) { + throw (std::runtime_error + (tostr("tokenizer::assemble_token", + ": duplicate decimal point", + xtag("pos", ix - tk_start), + xtag("char", *ix)))); + } + + period_flag = true; + } else if((*ix == 'e') || (*ix == 'E')) { + if (exponent_flag) { + throw (std::runtime_error + (tostr("tokenizer::assemble_token", + ": duplicate exponent marker", + xtag("pos", ix - tk_start), + xtag("char", *ix)))); + } + + exponent_flag = true; + } else if(isdigit(*ix)) { + if (exponent_flag) { + /* need digit before exponent to recognize as number */ + exponent_digit_flag = true; + } else { + number_flag = true; + } + } else { + /* invalid input */ + throw (std::runtime_error + (tostr("tokenizer::assemble_token", + ": unexpected character in numeric constant", + xtag("pos", ix - tk_start), + xtag("char", *ix)))); + } + } + + if (number_flag) { + if (period_flag || exponent_flag) { + tk_type = tokentype::tk_f64; + } else { + tk_type = tokentype::tk_i64; + } + } else if (period_flag && !exponent_flag) { + tk_type = tokentype::tk_dot; + } else { + /* not a valid token */ + } + + log && log(xtag("sign_flag", sign_flag)); + log && log(xtag("period_flag", period_flag), + xtag("exponent_flag", exponent_flag), + xtag("exponent_sign_flag", exponent_sign_flag), + xtag("number_flag", number_flag)); + log && log(xtag("tk_type", tk_type)); + + break; + } + case '"': + { + log && log("recognize string-token"); + + tk_type = tokentype::tk_string; + + tk_text.reserve(token_text.hi() - token_text.lo()); + + ++ix; /*skip initial " char*/ + + for (; ix != token_text.hi(); ++ix) { + log && log(xtag("*ix", *ix)); + + bool endofstring = false; + + switch(*ix) { + case '"': + endofstring = true; + + /* skip final " char, don't capture */ + ++ix; + + break; + case '\\': + /* skip escape char, don't capture */ + ++ix; + + if (ix == token_text.hi()) { + throw std::runtime_error + (tostr("tokenizer::assemble_token", + ": malformed string literal", + xtag("input", std::string_view(token_text.lo(), + token_text.hi())))); + } + + switch(*ix) { + case '\\': + log && log(xtag("*ix", *ix), xtag("escaped", "t")); + tk_text.push_back(*ix); + break; + case 'n': + log && log(xtag("*ix", *ix), xtag("newline", "t")); + tk_text.push_back('\n'); + break; + case 't': + log && log(xtag("*ix", *ix), xtag("tab", "t")); + tk_text.push_back('\t'); + break; + case 'r': + log && log(xtag("*ix", *ix), xtag("cr", "t")); + tk_text.push_back('\r'); + break; + case '"': + log && log(xtag("*ix", *ix), xtag("quote", "t")); + tk_text.push_back('"'); + break; + default: + throw std::runtime_error + (tostr("tokenizer::assemble_token", + ": unexpected \\-escaped char", + xtag("char", *ix))); + } + break; + default: + tk_text.push_back(*ix); + break; + } + + if (endofstring) + break; + } + + if (ix != token_text.hi()) { + throw std::runtime_error + (tostr("tokenizer::assemble_token", + ": expected \" to end string literal", + xtag("input", std::string_view(token_text.lo(), + token_text.hi())))); + } + + log && log(tostr("tokenizer::assemble_token", + xtag("tk_text", tk_text))); + + break; + } + case 'a': case 'A': + case 'b': case 'B': + case 'c': case 'C': + case 'd': case 'D': + case 'e': case 'E': + case 'f': case 'F': + case 'g': case 'G': + case 'h': case 'H': + case 'i': case 'I': + case 'j': case 'J': + case 'k': case 'K': + case 'l': case 'L': + case 'm': case 'M': + case 'n': case 'N': + case 'o': case 'O': + case 'p': case 'P': + case 'q': case 'Q': + case 'r': case 'R': + case 's': case 'S': + case 't': case 'T': + case 'u': case 'U': + case 'v': case 'V': + case 'w': case 'W': + case 'x': case 'X': + case 'y': case 'Y': + case 'z': case 'Z': + { + /* symbol/identifier must begin with a letter? + * we want to accept some other chars too. + * specifically want to allow identifiers: + * this-is-the-way + * this+is+also+the+way + * how/much/is/that/doggy + * put*an*asterisk*in*that + * something%special% + * + * like pure lisp, we don't allow: + * - identifier beginning with digit + * - period . + * + * unlike pure lisp, we don't allow anywhere in a symbol: + * - colon : + * - semicolon ; + * - comma , + * + * also we don't allow symbols to begin with special chars + */ + + tk_type = tokentype::tk_symbol; + break; + } + case '<': + tk_type = tokentype::tk_leftangle; + ++ix; + break; + case '>': + tk_type = tokentype::tk_rightangle; + ++ix; + break; + case '(': + tk_type = tokentype::tk_leftparen; + ++ix; + break; + case ')': + tk_type = tokentype::tk_rightparen; + ++ix; + break; + case '[': + tk_type = tokentype::tk_leftbracket; + ++ix; + break; + case ']': + tk_type = tokentype::tk_rightbracket; + ++ix; + break; + case '{': + tk_type = tokentype::tk_leftbrace; + ++ix; + break; + case '}': + tk_type = tokentype::tk_rightbrace; + ++ix; + break; + case ',': + tk_type = tokentype::tk_comma; + ++ix; + break; + case ';': + tk_type = tokentype::tk_semicolon; + ++ix; + break; + case ':': + tk_type = tokentype::tk_colon; + ++ix; + break; + case '=': + tk_type = tokentype::tk_singleassign; + ++ix; + break; + default: + break; + } + + if (tk_type == tokentype::tk_invalid) { + throw std::runtime_error(tostr("tokenizer::assemble_token", + ": unexpected input x", + xtag("x", *ix))); + } + + if ((tk_type == tokentype::tk_i64) + || (tk_type == tokentype::tk_f64) + || (tk_type == tokentype::tk_symbol)) + { + /* re-parse in token::i64_value() / token::f64_value() */ + tk_text = std::string(tk_start, tk_end); + } else if (tk_type == tokentype::tk_string) { + ; /* nothing to do here -- desired tk_text already constructed */ + } + + return token_type(tk_type, std::move(tk_text)); + } /*assemble_token*/ + + template + auto + tokenizer::scan(const span_type & input) -> scan_result + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("input", input)); + + const CharT * ix = input.lo(); + + /* skip whitespace */ + while (is_whitespace(*ix) && (ix != input.hi())) + ++ix; + + if(ix == input.hi()) { + /* no-op */ + return { + token_type::invalid(), + input.prefix(ix) + }; + } + + /* here: *ix is not whitespace */ + + auto whitespace = input.prefix(ix); + + log && log(xtag("whitespace.size", whitespace.size())); + + /* tk_start points to beginning of token + * (after any whitespace) + */ + const CharT * tk_start = ix; + + if (is_punctuation(*ix)) { + /* 1-character token */ + ++ix; + } else if (*ix == '"') { + bool complete_flag = false; + + /* 1. embedded space/tab allowed in string literal. + * 2. embedded newline/cr not allowed. + */ + CharT prev_ch = '"'; + + ++ix; + + for (; ix != input.hi(); ++ix) { + /* looking for unescaped " char to end literal */ + if (*ix == '"') { + if (prev_ch != '\\') { + ++ix; /* include terminating " for assemble_token */ + complete_flag = true; + break; + } + } else if ((*ix == '\n') || (*ix == '\r')) { + throw std::runtime_error + (tostr("tokenizer::scan", + ": must use \\n or \\r to encode newline/cr in" + " string literal")); + } + + prev_ch = *ix; + } + + if (!complete_flag) { + /* need more input to know if/when tokken complete */ + this->prefix_ += std::string(tk_start, input.hi()); + + log && log(xtag("captured-prefix", this->prefix_)); + } + } else { + /* scan until: + * - whitespace + * - punctuation + */ + for (; ix != input.hi(); ++ix) { + if (is_whitespace(*ix) || is_punctuation(*ix)) + break; + } + + if (ix == input.hi()) { + /* need more input to know if/when token complete */ + this->prefix_ += std::string(tk_start, input.hi()); + + log && log(xtag("captured-prefix", this->prefix_)); + } + } + + auto token_span = input.after_prefix(whitespace).prefix(ix); + + token tk + = (this->prefix_.empty() + ? assemble_token(token_span) + : token_type(tokentype::tk_invalid)); + + return scan_result + { tk, input.prefix(whitespace.size() + token_span.size()) }; + } /*scan*/ + + template + auto + tokenizer::notify_eof() -> token_type { + constexpr bool c_debug_flag = true; + + scope log(XO_DEBUG(c_debug_flag)); + + token tk + = (this->prefix_.empty() + ? token_type(tokentype::tk_invalid) + : assemble_token(span_type(&prefix_[0], + &prefix_[prefix_.size()]))); + + this->prefix_.clear(); + + return tk; + } /*notify_eof*/ + } /*namespace tok*/ +} /*namespace xo*/ + +/* end tokenizer.hpp */ diff --git a/include/xo/tokenizer/tokentype.hpp b/include/xo/tokenizer/tokentype.hpp new file mode 100644 index 00000000..cd890e38 --- /dev/null +++ b/include/xo/tokenizer/tokentype.hpp @@ -0,0 +1,142 @@ +/** @file tokentype.hpp + * + * author: Roland Conybeare, Jul 2024 + **/ + +#pragma once + +#include "xo/indentlog/print/tag.hpp" // for STRINGIFY +#include + +namespace xo { + namespace tok { + /** @enum tokentype + * @brief enum to identify different schematica input token types + * + * Schematica code examples: + * + * type point :: { xcoord : f64, ycoord: f64 }; + * type matrix :: array; // 2-d array + * + * decl hypot(x : f64, y : f64) -> f64; + * + * def hypot(x : f64, y : f64) { + * let + * x2 = (x * x); + * y2 = (y * y); + * hypot2 = (x2 + y2); + * in + * sqrt(hypot2); + * }; + * + * def someconst 4; + * + * def foo(v : vec) { + * def (pi : f64) = 3.1415926; + * def (h : (f64,f64) -> f64) = hypot; + * + * h = hypot3; + * }; + * + * def matrixproduct(x : matrix, y : matrix) { + * [i,j : x.row(i) * y.col(j)]; + * }; + **/ + enum class tokentype { + /** sentinel value **/ + tk_invalid = -1, + + /** an integer constant (signed 64-bit integer) **/ + tk_i64, + + /** a 64-bit floating-point constant **/ + tk_f64, + + /** a string literal **/ + tk_string, + + /** a symbol **/ + tk_symbol, + + /** left-hand parenthesis '(' **/ + tk_leftparen, + + /** right-hand parenthesis ')' **/ + tk_rightparen, + + /** left-hand bracket '[' **/ + tk_leftbracket, + + /** right-hand bracket ']' **/ + tk_rightbracket, + + /** left-hand brace '{' **/ + tk_leftbrace, + + /** right-hand brace '}' **/ + tk_rightbrace, + + /** left-hand angle bracket '<' **/ + tk_leftangle, + + /** right-hand angle bracket '>' **/ + tk_rightangle, + + /** dot '.' **/ + tk_dot, + + /** comma ',' **/ + tk_comma, + + /** colon ':' **/ + tk_colon, + + /** double-colon '::' **/ + tk_doublecolon, + + /** semi-colon ';' **/ + tk_semicolon, + + /** '=' **/ + tk_singleassign, + + /** ':=' **/ + tk_assign, + + /** '->' **/ + tk_yields, + + /** keyworkd 'type' **/ + tk_type, + + /** keyword 'def' **/ + tk_def, + + /** keyword 'lambda' **/ + tk_lambda, + + /** keyword 'if' **/ + tk_if, + + /** keyword 'let' **/ + tk_let, + + /** keyword 'in' **/ + tk_in, + + n_tokentype /* comes last, counts #of entries */ + }; /*tokentype*/ + + extern char const * + tokentype_descr(tokentype tk_type); + + inline std::ostream & + operator<< (std::ostream & os, tokentype tk_type) { + os << tokentype_descr(tk_type); + return os; + } + } /*namespace tok*/ +} /*namespace xo*/ + + +/* end tokentype.hpp */ diff --git a/src/tokenizer/CMakeLists.txt b/src/tokenizer/CMakeLists.txt new file mode 100644 index 00000000..6408d151 --- /dev/null +++ b/src/tokenizer/CMakeLists.txt @@ -0,0 +1,14 @@ +# tokenizer/CMakeLists.txt + +set(SELF_LIB tokenizer) +set(SELF_SRCS + tokentype.cpp + token.cpp) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +#xo_dependency(${SELF_LIB} refcnt) +xo_dependency(${SELF_LIB} indentlog) +#xo_dependency(${SELF_LIB} subsys) +#xo_boost_dependency(${SELF_LIB}) + +# end CMakeLists.txt diff --git a/src/tokenizer/token.cpp b/src/tokenizer/token.cpp new file mode 100644 index 00000000..6438dee1 --- /dev/null +++ b/src/tokenizer/token.cpp @@ -0,0 +1,9 @@ +/** @file token.cpp + * + * author: Roland Conybeare + **/ + +#include "token.hpp" +#include "xo/indentlog/print/tag.hpp" + +/** end token.cpp **/ diff --git a/src/tokenizer/tokentype.cpp b/src/tokenizer/tokentype.cpp new file mode 100644 index 00000000..228790ec --- /dev/null +++ b/src/tokenizer/tokentype.cpp @@ -0,0 +1,56 @@ +/* file tokentype.cpp + * + * author: Roland Conybeare + */ + +#include "tokentype.hpp" + +namespace xo { + namespace tok { + char const * + tokentype_descr(tokentype tk_type) + { +#define CASE(x) case tokentype::x: return STRINGIFY(x) + + switch(tk_type) { + CASE(tk_i64); + CASE(tk_f64); + CASE(tk_string); + CASE(tk_symbol); + CASE(tk_leftparen); + CASE(tk_rightparen); + CASE(tk_leftbracket); + CASE(tk_rightbracket); + CASE(tk_leftbrace); + CASE(tk_rightbrace); + CASE(tk_leftangle); + CASE(tk_rightangle); + CASE(tk_dot); + CASE(tk_comma); + CASE(tk_colon); + CASE(tk_doublecolon); + CASE(tk_semicolon); + CASE(tk_singleassign); + CASE(tk_assign); + CASE(tk_yields); + CASE(tk_type); + CASE(tk_def); + CASE(tk_lambda); + CASE(tk_if); + CASE(tk_let); + CASE(tk_in); + + case tokentype::tk_invalid: + case tokentype::n_tokentype: + return "?tokentype"; + } + +#undef CASE + + return "???"; + } /*tokentype_descr*/ + } /*namespace tok*/ +} /*namespace xo*/ + + +/* end tokentype.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..8dbd2ad2 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,13 @@ +# build unittest tokenizer/utest + +set(SELF_EXECUTABLE_NAME utest.tokenizer) +set(SELF_SOURCE_FILES + tokenizer_utest_main.cpp + tokenizer.test.cpp + token.test.cpp) + +xo_add_utest_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) +xo_self_dependency(${SELF_EXECUTABLE_NAME} tokenizer) +xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) + +# end CMakeLists.txt diff --git a/utest/token.test.cpp b/utest/token.test.cpp new file mode 100644 index 00000000..16b30399 --- /dev/null +++ b/utest/token.test.cpp @@ -0,0 +1,260 @@ +/* file token.test.cpp + * + * author: Roland Conybeare + */ + +#include "xo/tokenizer/token.hpp" +#include +#include + +namespace xo { + using token = xo::tok::token; + using xo::tok::tokentype; + + namespace ut { + struct testcase_i64 { + std::string text_; + bool expect_throw_; + std::int64_t expected_; + }; + + std::vector s_testcase_v = { + {"", true, 0}, + {"0", false, 0}, + {"-", true, 0}, + {"+", true, 0}, + {"-0", false, 0}, + {"+0", false, 0}, + {"1", false, 1}, + {"-1", false, -1}, + {"9", false, 9}, + {"-9", false, -9}, + {"12", false, 12}, + {"+12", false, 12}, + {"-12", false, -12}, + {"99", false, 99}, + {"-99", false, -99}, + {"123x", true, 0}, + }; + + TEST_CASE("parse-i64", "[token]") { + for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + INFO(xtag("i_tc", i_tc)); + + auto const & testcase = s_testcase_v[i_tc]; + + token tk(tokentype::tk_i64, + testcase.text_); + + REQUIRE(tk.tk_type() == tokentype::tk_i64); + + bool throw_flag = false; + try { + std::int64_t x = tk.i64_value(); + + REQUIRE(x == testcase.expected_); + } catch (std::exception & ex) { + throw_flag = true; + } + + REQUIRE(throw_flag == testcase.expect_throw_); + } + } + + TEST_CASE("error-i64", "[token]") { + token tk(tokentype::tk_i64, "+"); + + bool throw_flag = false; + + try { + tk.i64_value(); + } catch(std::exception & ex) { + throw_flag = true; + } + + REQUIRE(throw_flag); + } + + namespace { + struct testcase_f64 { + std::string text_; + bool expect_throw_; + double expected_; + }; + + std::vector s_testcase_v = { + {"", true, 0}, + {"0", false, 0}, + {"-", true, 0}, + {"+", true, 0}, + {"-0", false, 0}, + + {"+0", false, 0}, + {"1", false, 1}, + {"-1", false, -1}, + {"9", false, 9}, + {"-9", false, -9}, + + {"12", false, 12}, + {"+12", false, 12}, + {"-12", false, -12}, + {"99", false, 99}, + {"-99", false, -99}, + + {"123x", true, 0}, + {"0.0", false, 0.0}, + {"0.1", false, 0.1}, + {"0.12", false, 0.12}, + {"0.123", false, 0.123}, + + {"0.1234", false, 0.1234}, + {"0.12345", false, 0.12345}, + {"0.123456", false, 0.123456}, + {"0.1234567", false, 0.1234567}, + {"0.12345678", false, 0.12345678}, + + {"0.123456789", false, 0.123456789}, + {"+0.0", false, 0.0}, + {"+0.1", false, 0.1}, + {"+0.12", false, 0.12}, + {"+0.123", false, 0.123}, + + {"+0.1234", false, 0.1234}, + {"+0.12345", false, 0.12345}, + {"+0.123456", false, 0.123456}, + {"+0.1234567", false, 0.1234567}, + {"+0.12345678", false, 0.12345678}, + + {"+0.123456789", false, 0.123456789}, + {"+0.0e0", false, 0.0}, + {"+0.1e0", false, 0.1}, + {"+0.12e0", false, 0.12}, + {"+0.123e0", false, 0.123}, + + {"+0.1234e0", false, 0.1234}, + {"+0.12345e0", false, 0.12345}, + {"+0.123456e0", false, 0.123456}, + {"+0.1234567e0", false, 0.1234567}, + {"+0.12345678e0", false, 0.12345678}, + + {"+0.123456789e0", false, 0.123456789}, + {"+0.0e1", false, 00.}, + {"+0.1e1", false, 01.}, + {"+0.12e1", false, 01.2}, + {"+0.123e1", false, 01.23}, + + {"+0.1234e1", false, 01.234}, + {"+0.12345e1", false, 01.2345}, + {"+0.123456e1", false, 01.23456}, + {"+0.1234567e1", false, 01.234567}, + {"+0.12345678e1", false, 01.2345678}, + + {"+0.123456789e1", false, 01.23456789}, + {"+0.0E1", false, 00.}, + {"+0.1E1", false, 01.}, + {"+0.12E1", false, 01.2}, + {"+0.123E1", false, 01.23}, + + {"+0.1234E1", false, 01.234}, + {"+0.12345E1", false, 01.2345}, + {"+0.123456E1", false, 01.23456}, + {"+0.1234567E1", false, 01.234567}, + {"+0.12345678E1", false, 01.2345678}, + + {"+0.123456789E1", false, 01.23456789}, + {"+0.0e9", false, 0.0}, + {"+0.1e9", false, 0.1e9}, + {"+0.12e9", false, 0.12e9}, + {"+0.123e9", false, 0.123e9}, + + {"+0.1234e9", false, 0.1234e9}, + {"+0.12345e9", false, 0.12345e9}, + {"+0.123456e9", false, 0.123456e9}, + {"+0.1234567e9", false, 0.1234567e9}, + {"+0.12345678e9", false, 0.12345678e9}, + + {"+0.123456789e9", false, 0.123456789e9}, + {"-0.0", false, -0.0}, + {"-0.1", false, -0.1}, + {"-0.12", false, -0.12}, + {"-0.123", false, -0.123}, + + {"-0.1234", false, -0.1234}, + {"-0.12345", false, -0.12345}, + {"-0.123456", false, -0.123456}, + {"-0.1234567", false, -0.1234567}, + {"-0.12345678", false, -0.12345678}, + + {"-0.123456789", false, -0.123456789}, + {"00.", false, 0.0}, + {"01.", false, 1.0}, + {"01.2", false, 1.2}, + {"01.23", false, 1.23}, + + {"01.234", false, 1.234}, + {"01.2345", false, 1.2345}, + {"01.23456", false, 1.23456}, + {"01.234567", false, 1.234567}, + {"01.2345678", false, 1.2345678}, + + {"01.23456789", false, 1.23456789}, + {"0.0", false, 0.0}, + {"1.2", false, 1.2}, + {"12.", false, 12.0}, + {"12.3", false, 12.3}, + + {"12.34", false, 12.34}, + {"12.345", false, 12.345}, + {"12.3456", false, 12.3456}, + {"12.34567", false, 12.34567}, + {"12.345678", false, 12.345678}, + + {"12.3456789", false, 12.3456789}, + {"01.23", false, 1.23}, + {"12.3", false, 12.3}, + {"123.", false, 123.0}, + {"123.4", false, 123.4}, + + {"123.45", false, 123.45}, + {"123.456", false, 123.456}, + {"123.4567", false, 123.4567}, + {"123.45678", false, 123.45678}, + {"123.456789", false, 123.456789}, + }; + + TEST_CASE("parse-f64", "[token]") { + for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + auto const & testcase = s_testcase_v[i_tc]; + + INFO(tostr(xtag("i_tc", i_tc), + xtag("text", testcase.text_) + )); + + token tk(tokentype::tk_f64, + testcase.text_); + + REQUIRE(tk.tk_type() == tokentype::tk_f64); + + bool throw_flag = false; + std::string ex_msg; + + try { + double x = tk.f64_value(); + + REQUIRE(x == Approx(testcase.expected_).epsilon(1.0e-15)); + } catch (std::exception & ex) { + ex_msg = ex.what(); + + throw_flag = true; + } + + INFO(xtag("ex_msg", ex_msg)); + + REQUIRE(throw_flag == testcase.expect_throw_); + } + } + } /*namespace*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end token.test.cpp */ diff --git a/utest/tokenizer.test.cpp b/utest/tokenizer.test.cpp new file mode 100644 index 00000000..dff86541 --- /dev/null +++ b/utest/tokenizer.test.cpp @@ -0,0 +1,160 @@ +/* file tokenizer.test.cpp + * + * author: Roland Conybeare + */ + +#include "tokenizer.hpp" +#include + +namespace xo { + using xo::tok::tokentype; + using token = xo::tok::token; + using xo::tok::span; + + namespace ut { + namespace { + struct testcase_tkz { + std::string input_; + bool expect_throw_; + token expected_tk_; + bool consume_all_; + }; + + std::vector + s_testcase_v = { + {"<", false, token::leftangle(), true}, + {">", false, token::rightangle(), true}, + + {"(", false, token::leftparen(), true}, + {")", false, token::rightparen(), true}, + + {"[", false, token::leftbracket(), true}, + {"]", false, token::rightbracket(), true}, + + {"{", false, token::leftbrace(), true}, + {" {", false, token::leftbrace(), true}, + + {"\t{", false, token::leftbrace(), true}, + {"\n{", false, token::leftbrace(), true}, + {"}", false, token::rightbrace(), true}, + + {"0", false, token::i64_token("0"), true}, + {"1", false, token::i64_token("1"), true}, + {"12", false, token::i64_token("12"), true}, + {"123", false, token::i64_token("123"), true}, + {"1234", false, token::i64_token("1234"), true}, + + {"0 ", false, token::i64_token("0"), false}, + {"1 ", false, token::i64_token("1"), false}, + {"12 ", false, token::i64_token("12"), false}, + {"123 ", false, token::i64_token("123"), false}, + {"1234 ", false, token::i64_token("1234"), false}, + + {"1<", false, token::i64_token("1"), false}, + {"1>", false, token::i64_token("1"), false}, + {"1(", false, token::i64_token("1"), false}, + {"1)", false, token::i64_token("1"), false}, + {"1[", false, token::i64_token("1"), false}, + {"1]", false, token::i64_token("1"), false}, + {"1{", false, token::i64_token("1"), false}, + {"1}", false, token::i64_token("1"), false}, + {"1;", false, token::i64_token("1"), false}, + {"1:", false, token::i64_token("1"), false}, + {"1,", false, token::i64_token("1"), false}, + + {".1", false, token::f64_token(".1"), true}, + {".12", false, token::f64_token(".12"), true}, + {".123", false, token::f64_token(".123"), true}, + + {"+.1", false, token::f64_token("+.1"), true}, + {"+.12", false, token::f64_token("+.12"), true}, + {"+.123", false, token::f64_token("+.123"), true}, + + {"-.1", false, token::f64_token("-.1"), true}, + {"-.12", false, token::f64_token("-.12"), true}, + {"-.123", false, token::f64_token("-.123"), true}, + + {"1.", false, token::f64_token("1."), true}, + {"1.2", false, token::f64_token("1.2"), true}, + {"1.23", false, token::f64_token("1.23"), true}, + + {"1e0", false, token::f64_token("1e0"), true}, + {"1e-1", false, token::f64_token("1e-1"), true}, + {"1e1", false, token::f64_token("1e1"), true}, + {"1e+1", false, token::f64_token("1e+1"), true}, + + {"\"hello\"", false, token::string_token("hello"), true}, + /* tokenizer sees this input: + * "\"hi\", she said" + */ + {"\"\\\"hi\\\", she said\"", false, token::string_token("\"hi\", she said"), true}, + /* tokenizer sees this input: + * "look ma, newline ->\n<- " + */ + {"\"look ma, newline ->\\n<- \"", false, + token::string_token("look ma, newline ->\n<- "), true}, + /* tokenizer sees this input: + * "tab to the right [\t], to the right [\t]" + */ + {"\"tab to the right [\\t], to the right [\\t]\"", false, + token::string_token("tab to the right [\t], to the right [\t]"), true}, + + {"symbol", false, token::symbol_token("symbol"), true}, + }; + } + + TEST_CASE("tokenizer", "[tokenizer]") { + for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + const testcase_tkz & testcase = s_testcase_v[i_tc]; + + INFO(xtag("input", testcase.input_)); + INFO(xtag("i_tc", i_tc)); + + using tokenizer + = xo::tok::tokenizer; + + tokenizer tkz; + tokenizer::span_type + in_span(testcase.input_.c_str(), + testcase.input_.c_str() + testcase.input_.size()); + + auto out = tkz.scan(in_span); + + auto tk = out.first; + + if (tk.is_invalid()) + tk = tkz.notify_eof(); + + REQUIRE(tk.tk_type() == testcase.expected_tk_.tk_type()); + if (tk.tk_type() == tokentype::tk_i64) + { + REQUIRE(!tk.text().empty()); + REQUIRE(tk.i64_value() == testcase.expected_tk_.i64_value()); + } else if (tk.tk_type() == tokentype::tk_f64) + { + REQUIRE(!tk.text().empty()); + REQUIRE(tk.f64_value() == testcase.expected_tk_.f64_value()); + } else if(tk.tk_type() == tokentype::tk_string) + { + /* tk.text() can be empty, consider input "" */ + REQUIRE(tk.text() == testcase.expected_tk_.text()); + } else if(tk.tk_type() == tokentype::tk_symbol) + { + REQUIRE(!tk.text().empty()); + REQUIRE(tk.text() == testcase.expected_tk_.text()); + } else { + REQUIRE(tk.text().empty()); + } + + /* must consume all input for tests we're doing here */ + if (testcase.consume_all_) + REQUIRE(out.second == in_span); + else + REQUIRE(out.second != in_span); + } + } + + } /*namespace ut*/ +} /*namespace xo*/ + +/* end tokenizer.test.cpp */ diff --git a/utest/tokenizer_utest_main.cpp b/utest/tokenizer_utest_main.cpp new file mode 100644 index 00000000..c5e273c4 --- /dev/null +++ b/utest/tokenizer_utest_main.cpp @@ -0,0 +1,6 @@ +/* file tokenizer_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/* end tokenizer_utest_main.cpp */ From 04920c0e9a51ed0a5d194410917dca95ffb06c55 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 23 Jul 2024 12:05:39 +1000 Subject: [PATCH 1241/2524] xo-tokenizer: build: canonical lib/proj names, + xo_ prefix --- CMakeLists.txt | 2 +- cmake/{tokenizerConfig.cmake.in => xo_tokenizerConfig.cmake.in} | 0 src/tokenizer/CMakeLists.txt | 2 +- utest/CMakeLists.txt | 2 +- utest/tokenizer.test.cpp | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename cmake/{tokenizerConfig.cmake.in => xo_tokenizerConfig.cmake.in} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d6500a69..147e16c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10) -project(tokenizer VERSION 0.1) +project(xo_tokenizer VERSION 0.1) include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) diff --git a/cmake/tokenizerConfig.cmake.in b/cmake/xo_tokenizerConfig.cmake.in similarity index 100% rename from cmake/tokenizerConfig.cmake.in rename to cmake/xo_tokenizerConfig.cmake.in diff --git a/src/tokenizer/CMakeLists.txt b/src/tokenizer/CMakeLists.txt index 6408d151..cad846f4 100644 --- a/src/tokenizer/CMakeLists.txt +++ b/src/tokenizer/CMakeLists.txt @@ -1,6 +1,6 @@ # tokenizer/CMakeLists.txt -set(SELF_LIB tokenizer) +set(SELF_LIB xo_tokenizer) set(SELF_SRCS tokentype.cpp token.cpp) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 8dbd2ad2..cc080294 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -7,7 +7,7 @@ set(SELF_SOURCE_FILES token.test.cpp) xo_add_utest_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) -xo_self_dependency(${SELF_EXECUTABLE_NAME} tokenizer) +xo_self_dependency(${SELF_EXECUTABLE_NAME} xo_tokenizer) xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) # end CMakeLists.txt diff --git a/utest/tokenizer.test.cpp b/utest/tokenizer.test.cpp index dff86541..cb796f47 100644 --- a/utest/tokenizer.test.cpp +++ b/utest/tokenizer.test.cpp @@ -3,7 +3,7 @@ * author: Roland Conybeare */ -#include "tokenizer.hpp" +#include "xo/tokenizer/tokenizer.hpp" #include namespace xo { From 0af24e9a2cf441c7eff156493aac23ef1e053b78 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 23 Jul 2024 12:07:05 +1000 Subject: [PATCH 1242/2524] xo-tokenizer: + .gitignore --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3d3a7826 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# emacs workspace config +.projectile +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json From 5d2ee35fe67825b259642f5ee65408f4b0621f1f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 31 Jul 2024 23:37:51 +1000 Subject: [PATCH 1243/2524] parser: initial implementation [wip - only handles 'def' expr --- .gitignore | 8 + CMakeLists.txt | 27 ++ cmake/xo-bootstrap-macros.cmake | 35 ++ cmake/xo_parserConfig.cmake.in | 8 + include/xo/parser/parser.hpp | 454 +++++++++++++++++++++ src/parser/CMakeLists.txt | 11 + src/parser/parser.cpp | 690 ++++++++++++++++++++++++++++++++ utest/CMakeLists.txt | 16 + utest/parser.test.cpp | 199 +++++++++ utest/parser_utest_main.cpp | 6 + 10 files changed, 1454 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_parserConfig.cmake.in create mode 100644 include/xo/parser/parser.hpp create mode 100644 src/parser/CMakeLists.txt create mode 100644 src/parser/parser.cpp create mode 100644 utest/CMakeLists.txt create mode 100644 utest/parser.test.cpp create mode 100644 utest/parser_utest_main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3d3a7826 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# emacs workspace config +.projectile +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..84eccb39 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,27 @@ +# xo-parser/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_parser VERSION 0.1) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings + +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- + +add_subdirectory(src/parser) +add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/cmake/xo_parserConfig.cmake.in b/cmake/xo_parserConfig.cmake.in new file mode 100644 index 00000000..6eadeb07 --- /dev/null +++ b/cmake/xo_parserConfig.cmake.in @@ -0,0 +1,8 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(xo_expression) +find_dependency(xo_tokenizer) +#find_dependency(subsys) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp new file mode 100644 index 00000000..92b8e046 --- /dev/null +++ b/include/xo/parser/parser.hpp @@ -0,0 +1,454 @@ +/* file parser.hpp + * + * author: Roland Conybeare, Jul 2024 + */ + +#pragma once + +#include "xo/expression/Expression.hpp" +#include "xo/tokenizer/token.hpp" +#include +#include + +namespace xo { + namespace scm { + // ----- exprir ----- + + enum class exprirtype { + invalid = -1, + + empty, + symbol, + expression, + + n_exprirtype + }; + + extern const char * + exprirtype_descr(exprirtype x); + + inline std::ostream & + operator<< (std::ostream & os, + exprirtype x) + { + os << exprirtype_descr(x); + return os; + } + + /** intermediate representation for some part of an expression + * + * Examples: + * 1. a variable name (but without type information) + **/ + class exprir { + public: + using Expression = xo::ast::Expression; + + public: + exprir() = default; + exprir(exprirtype xir_type, + const std::string & x) + : xir_type_{xir_type}, symbol_name_{x} {} + exprir(exprirtype xir_type, + rp expr) + : xir_type_{xir_type}, expr_{std::move(expr)} {} + + exprirtype xir_type() const { return xir_type_; } + const std::string & symbol_name() const { return symbol_name_; } + const rp & expr() const { return expr_; } + + void print(std::ostream & os) const; + + private: + /** IR type code **/ + exprirtype xir_type_ = exprirtype::invalid; + /** xir_type=symbol: a symbol (type or variable) name **/ + std::string symbol_name_; + /** xir_type=expression: a completed expression **/ + rp expr_; + }; + + inline std::ostream & + operator<< (std::ostream & os, const exprir & x) { + x.print(os); + return os; + } + + enum class exprstatetype { + invalid = -1, + + /** toplevel of some translation unit **/ + expect_toplevel_expression_sequence, + + def_0, + def_1, + def_2, + def_3, + def_4, + + expect_rhs_expression, + expect_symbol, + + n_exprstatetype + }; + + extern const char * + exprstatetype_descr(exprstatetype x); + + inline std::ostream & + operator<< (std::ostream & os, exprstatetype x) { + os << exprstatetype_descr(x); + return os; + } + + enum class expractiontype { + invalid = -1, + + push1, + push2, + keep, + emit, + pop, + + n_expractiontype + }; + + extern const char * + expractiontype_descr(expractiontype x); + + inline std::ostream & + operator<< (std::ostream & os, expractiontype x) { + os << expractiontype_descr(x); + return os; + } + + /** an action associated with parser response to an incoming lexical + **/ + class expraction { + public: + expraction() = default; + expraction(expractiontype action_type, + const exprir & expr_ir, + exprstatetype push_exs1, + exprstatetype push_exs2) + : action_type_{action_type}, expr_ir_{expr_ir}, + push_exs1_{push_exs1}, push_exs2_{push_exs2} + {} + + static expraction keep(); + static expraction emit(const exprir & ir); + static expraction push2(exprstatetype s1, exprstatetype s2); + + expractiontype action_type() const { return action_type_; } + const exprir & expr_ir() const { return expr_ir_; } + exprstatetype push_exs1() const { return push_exs1_; } + exprstatetype push_exs2() const { return push_exs2_; } + + void print(std::ostream & os) const; + + private: + /** + * push1: push new exprstate built from push_exs1_ + * push2: push new exprstate built from push_exs1_, + * followed by push_exs2_ + * keep: keep current exprstate (which will have updated inplace) + * pop: drop exprstate, report exprir to parent + **/ + expractiontype action_type_ = expractiontype::invalid; + /** + * intermediate representation (pass to enclosing stack state) + **/ + exprir expr_ir_; + /** with action_type push1 or push2, + * parser will push exprstate with this type + **/ + exprstatetype push_exs1_ = exprstatetype::invalid; + /** with action_type push2, + * parser will push exprstate with this type + * (after pushing exprstate built from push_exs1_) + **/ + exprstatetype push_exs2_ = exprstatetype::invalid; + }; + + inline std::ostream & + operator<< (std::ostream & os, + const expraction & x) + { + x.print(os); + return os; + } + + /** state associated with a partially-parsed expression. + **/ + class exprstate { + public: + using exprtype = xo::ast::exprtype; + using token_type = token; + + public: + exprstate() = default; + exprstate(exprstatetype exs_type) : exs_type_{exs_type} {} + + static exprstate expect_toplevel_expression_sequence() { + return exprstate(exprstatetype::expect_toplevel_expression_sequence); + } + static exprstate def_0() { + return exprstate(exprstatetype::def_0); + } + static exprstate expect_symbol() { + return exprstate(exprstatetype::expect_symbol); + } + + exprstatetype exs_type() const { return exs_type_; } + + /** true iff this parsing state admits a 'def' keyword + * as next token + **/ + bool admits_definition() const; + /** true iff this parsing state admits a symbol as next token **/ + bool admits_symbol() const; + /** true iff this parsing state admits a colon as next token **/ + bool admits_colon() const; + /** true iff this parsing state admits a singleassign '=' as next token **/ + bool admits_singleassign() const; + /** true iff this parsing state admits a 64-bit floating point literal token **/ + bool admits_f64() const; + + /** update exprstate in response to incoming token @p tk, + * forward instructions to parent parser + **/ + expraction on_input(const token_type & tk); + /** update exprstate in response to IR (intermediate representation) + * from nested parsing task + **/ + expraction on_exprir(const exprir & ir); + + /** print human-readable representation on @p os **/ + void print(std::ostream & os) const; + + private: + expraction on_def(); + expraction on_symbol(const token_type & tk); + expraction on_colon(); + expraction on_singleassign(); + expraction on_f64(const token_type & tk); + + private: + /** + * def foo : f64 = 1 + * ^ ^ ^ ^ ^ ^ ^ + * | | | | | | (done) + * | | | | | def_4:expect_rhs_expression + * | | | | def_3 + * | | | def_2:expect_symbol + * | | def_1 + * | def_0:expect_symbol + * expect_toplevel_expression_sequence + * + * def_0:expect_symbol: got 'def' keyword, symbol to follow + * def_1: got symbol name + * def_2:expect_symbol got (optional) colon, type name to follow + * def_3: got symbol type + * def_4:expect_rhs_expression got (optional) equal sign, value to follow + * (done): definition complete, pop exprstate from stack + * + **/ + exprstatetype exs_type_; + + /** e.g. foo in + * def foo : f64 = 1 + **/ + std::string def_lhs_symbol_; + /** e.g. f64 in + * def foo : f64 = 1 + **/ + std::string def_lhs_type_; + }; /*exprstate*/ + + inline std::ostream & + operator<< (std::ostream & os, const exprstate & x) { + x.print(os); + return os; + } + + /** schematica parser + * + * Examples: + * + * decltype point + * + * // forward declarations + * decl pi : f64 + * decl fib(n : i32) -> i32 + * + * def pi = 3.14159265 // constant. = is single assignment + * + * def fib(n : i32) -> i32 { + * // nested defs ok + * def aux(n : i32, s1 : i32, s2 : i32) -> i32 { + * // or: + * // (n == 0) ? s1 : aux(n - 1, s1 + s2, s1) + * // + * if (n == 0) { + * s1 + * } else { + * aux(n - 1, s1 + s2, s1) + * } + * + * // or: + * // if (n == 0) ? s1 : aux(n - 1, s1 + s2, s1) + * } + * + * aux(n=n, s1=1, s2=0) + * } + * + * def anotherfib = lambda(n : i32) { fib(n) } + * + * def any : object + * def l : list = '() + * + * deftype point :: {x : f64, y : f64} + * deftype polar :: {arg : f64, mag : f64} + * + * def polar2rect(pt : polar) -> point { + * point(x = pt.mag * cos(arg), + * y = pt.mag * sin(arg)) + * } + * + * Grammar: + * toplevel-program = expression* + * type-decl = decltype $typename [<$tp1 .. $tpn>] + * expression = define-expr + * | literal-expr + * | variable-expr + * | apply-expr + * | if-expr + * | lambda-expr + * | block + * + * define-expr = type-decl + * | type-def + * | variable-def + * | function-decl + * | function-def + * + * type-def = deftype $typename [<$tp1 .. $tpn>] :: type-def-rhs + * type-def-rhs = object + * | bool + * | i128 | i64 | i32 | i16 | i8 + * | f128 | f64 | f32 | f16 + * | struct $typename { ($membername(i) : $typename(i))* } + * [end $typename] + * | tuple $typename { $typename(1), .., $typename(n) } + * [end $typename] + * | copytype $typename + * | subtype $typename { ($member(i) : $typename(i))* } + * + * variable-def = decl $varname [: $typename] [= expression] + * function-decl = decl $functionname($varname(1) : $typename(1), + * .., + * $varname(n) : $typename(n)) -> $typename[ret] + * function-def = def $functionname($varname(1) : $typename(1), + * .., + * $varname(n) : $typename(n)) [-> $typename[ret]] + * body-expr + * [ end $functionname ] + * literal-expr = integer-literal + * | fp-literal + * | string-literal + * | symbol-literal + * | struct-literal + * + * variable-expr = $varname + * apply-expr = fn-expr(arg-expr(1), .., arg-expr(n)) + * fn-expr = expression + * arg-expr(i) = expression + * + * if-expr = if (test-expr) then-block else else-block + * | (test-expr) ? then-expr : else-expr + * test-expr = expression + * then-block = block + * else-block = block + * + * block = { (definition | expression)* } + * + * lambda-expr = lambda ($paramname(1) : $type(1), + * .., + * $paramname(n) : $type(n)) body-expr + * body-expr = expression + **/ + class parser { + public: + using Expression = xo::ast::Expression; + using token_type = exprstate::token_type; // token; + + public: + /** create parser in initial state; + * parser is ready to receive tokens via @ref include_token + **/ + parser() = default; + + /** for diagnostics: number of entries in parser stack **/ + std::size_t stack_size() const { return stack_.size(); } + /** for diagnostics: exprstatetype at level @p i + * (taken relative to top of stack) + * + * @pre 0 <= i < stack_size + **/ + exprstatetype i_exstype(std::size_t i) const { + std::size_t z = stack_.size(); + + if (i < z) { + return stack_[(z - 1) - i].exs_type(); + } + + /* out of bounds */ + return exprstatetype::invalid; + } + + /** put parser into state for beginning of a translation unit + * (i.e. input stream) + **/ + void begin_translation_unit(); + + /** include next token @p tk and increment parser state. + * + * @param tk next input token + * @return parsed expression, if @p tk completes an expression. + * otherwise nullptr + **/ + rp include_token(const token_type & tk); + + /** print human-readable representation on stream @p os **/ + void print(std::ostream & os) const; + + private: + exprstate & top_exprstate(); + void push_exprstate(const exprstate & exs); + void pop_exprstate(); + + private: + /** state recording state associated with enclosing expressions. + * + * Note: at least asof c++23, the std::stack api doesn't support access + * to members other than the top. + * + * for stack with N elements (N = stack_.size()): + * - bottom of stack is stack_[0] + * - top of stack is stack_[N-1] + **/ + std::vector stack_; + + }; /*parser*/ + + inline std::ostream & + operator<< (std::ostream & os, + const parser & x) { + x.print(os); + return os; + } + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end parser.hpp */ diff --git a/src/parser/CMakeLists.txt b/src/parser/CMakeLists.txt new file mode 100644 index 00000000..2af43636 --- /dev/null +++ b/src/parser/CMakeLists.txt @@ -0,0 +1,11 @@ +# parser/CMakeLists.txt + +set(SELF_LIB xo_parser) +set(SELF_SRCS + parser.cpp) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_dependency(${SELF_LIB} xo_expression) +xo_dependency(${SELF_LIB} xo_tokenizer) + +# end CMakeLists.txt diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp new file mode 100644 index 00000000..ab6404fd --- /dev/null +++ b/src/parser/parser.cpp @@ -0,0 +1,690 @@ +/* file parser.cpp + * + * author: Roland Conybeare + */ + +#include "parser.hpp" +#include "xo/expression/DefineExpr.hpp" +#include "xo/expression/Constant.hpp" +#include +#include + +namespace xo { + using xo::ast::Expression; + using xo::ast::DefineExpr; + using xo::ast::Constant; + + namespace scm { + const char * + exprirtype_descr(exprirtype x) { + switch(x) { + case exprirtype::invalid: + return "?invalid"; + case exprirtype::empty: + return "empty"; + case exprirtype::symbol: + return "symbol"; + case exprirtype::expression: + return "expression"; + case exprirtype::n_exprirtype: + break; + } + + return "???exprirtype"; + } + + void + exprir::print(std::ostream & os) const { + os << ""; + } + + const char * + exprstatetype_descr(exprstatetype x) { + switch(x) { + case exprstatetype::invalid: + return "?invalid"; + case exprstatetype::expect_toplevel_expression_sequence: + return "expect_toplevel_expression_sequence"; + case exprstatetype::def_0: + return "def_0"; + case exprstatetype::def_1: + return "def_1"; + case exprstatetype::def_2: + return "def_2"; + case exprstatetype::def_3: + return "def_3"; + case exprstatetype::def_4: + return "def_4"; + case exprstatetype::expect_rhs_expression: + return "expect_rhs_expression"; + case exprstatetype::expect_symbol: + return "expect_symbol"; + case exprstatetype::n_exprstatetype: + break; + } + + return "???"; + } + + const char * + expractiontype_descr(expractiontype x) { + switch(x) { + case expractiontype::invalid: + return "?invalid"; + case expractiontype::push1: + return "push1"; + case expractiontype::push2: + return "push2"; + case expractiontype::keep: + return "keep"; + case expractiontype::emit: + return "emit"; + case expractiontype::pop: + return "pop"; + case expractiontype::n_expractiontype: + break; + } + + return "???"; + } + + expraction + expraction::keep() { + return expraction(expractiontype::keep, + exprir(), + exprstatetype::invalid /*not used*/, + exprstatetype::invalid /*not used*/); + } + + expraction + expraction::emit(const exprir & ir) { + return expraction(expractiontype::emit, + ir, + exprstatetype::invalid /*not used*/, + exprstatetype::invalid /*not used*/); + } + + expraction + expraction::push2(exprstatetype s1, + exprstatetype s2) { + return expraction(expractiontype::push2, + exprir(), + s1, + s2); + } + + void + expraction::print(std::ostream & os) const { + os << ""; + } + + bool + exprstate::admits_definition() const { + switch(exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + return true; + + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + /* note for def_4: + * rhs could certainly be a function body that contains + * nested defines; but then immediately-enclosing-exprstate + * would be a block + */ + return false; + case exprstatetype::expect_rhs_expression: + return false; + case exprstatetype::expect_symbol: + return false; + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + return false; + } + } + + bool + exprstate::admits_symbol() const { + switch(exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + return false; + + case exprstatetype::expect_rhs_expression: + /* treat symbol as variable name */ + return true; + + case exprstatetype::expect_symbol: + return true; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + return false; + } + } + + bool + exprstate::admits_colon() const { + switch(exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + case exprstatetype::def_0: + return false; + + case exprstatetype::def_1: + return true; + + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::expect_rhs_expression: + /* rhs-expressions (or expressions for that matter) + * may not begin with a colon + */ + case exprstatetype::expect_symbol: + return false; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + return false; + } + } + + bool + exprstate::admits_singleassign() const { + switch(exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + return false; + + case exprstatetype::def_3: + return true; + + case exprstatetype::def_4: + case exprstatetype::expect_rhs_expression: + /* rhs-expressions (or expressions for that matter) + * may not begin with singleassign '=' + */ + case exprstatetype::expect_symbol: + return false; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + return false; + } + } + + bool + exprstate::admits_f64() const { + switch(exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + return false; + + case exprstatetype::expect_rhs_expression: + return true; + + case exprstatetype::expect_symbol: + return false; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + return false; + } + } + + expraction + exprstate::on_def() { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_def"; + + /* lots of illegal states */ + if (!this->admits_definition()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected keyword 'def' for parsing state", + xtag("state", *this))); + } + + /* keyword 'def' introduces a definition: + * def pi : f64 = 3.14159265 + * def sq(x : f64) -> f64 { (x * x) } + */ + return expraction::push2(exprstatetype::def_0, + /* todo: replace: + * expect_symbol_or_function_signature() + */ + exprstatetype::expect_symbol); + } + + expraction + exprstate::on_symbol(const token_type & tk) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_symbol"; + + if (!this->admits_symbol()) { + throw std::runtime_error + (tostr(self_name, + ": unexpected symbol-token for parsing state", + xtag("symbol", tk), + xtag("state", *this))); + } + + switch(this->exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + throw std::runtime_error + (tostr(self_name, + ": unexpected symbol-token at top-level", + " (expecting decl|def)", + xtag("symbol", tk))); + break; + + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + /* unreachable */ + assert(false); + return expraction(); + + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_symbol: + return expraction(expractiontype::pop, + exprir(exprirtype::symbol, tk.text()), + exprstatetype::invalid /*not used*/, + exprstatetype::invalid /*not used*/); + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return expraction(); + } + } + + expraction + exprstate::on_colon() { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_colon"; + + /* lots of illegal states */ + if (!this->admits_colon()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected colon for parsing state", + xtag("state", *this))); + } + + if (this->exs_type_ == exprstatetype::def_1) { + this->exs_type_ = exprstatetype::def_2; + + return expraction(expractiontype::push1, + exprir(), + exprstatetype::expect_symbol, + exprstatetype::invalid /*not used*/); + } else { + assert(false); + return expraction(); + } + } + + expraction + exprstate::on_singleassign() { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_singleassign"; + + if (!this->admits_singleassign()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected equals for parsing state", + xtag("state", *this))); + } + + if (this->exs_type_ == exprstatetype::def_3) { + this->exs_type_ = exprstatetype::def_4; + + return expraction(expractiontype::push1, + exprir(), + exprstatetype::expect_rhs_expression, + exprstatetype::invalid /*not used*/); + } else { + assert(false); + return expraction(); + } + } + + expraction + exprstate::on_f64(const token_type & tk) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_f64"; + + if (!this->admits_f64()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected floating-point literal for parsing state", + xtag("state", *this))); + } + + if (this->exs_type_ == exprstatetype::expect_rhs_expression) { + return expraction(expractiontype::pop, + exprir(exprirtype::expression, + Constant::make(tk.f64_value())), + exprstatetype::invalid /*not used*/, + exprstatetype::invalid /*not used*/); + } else { + assert(false); + return expraction(); + } + } + + expraction + exprstate::on_input(const token_type & tk) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + log && log(xtag("tk", tk)); + log && log(xtag("state", *this)); + + switch(tk.tk_type()) { + + case tokentype::tk_def: + return this->on_def(); + + case tokentype::tk_i64: + assert(false); + return expraction(); + + case tokentype::tk_f64: + return this->on_f64(tk); + + case tokentype::tk_string: + assert(false); + return expraction(); + + case tokentype::tk_symbol: + return this->on_symbol(tk); + + case tokentype::tk_leftparen: + + case tokentype::tk_rightparen: + case tokentype::tk_leftbracket: + case tokentype::tk_rightbracket: + case tokentype::tk_leftbrace: + case tokentype::tk_rightbrace: + + case tokentype::tk_leftangle: + case tokentype::tk_rightangle: + case tokentype::tk_dot: + case tokentype::tk_comma: + assert(false); + return expraction(); + + case tokentype::tk_colon: + return this->on_colon(); + + case tokentype::tk_doublecolon: + case tokentype::tk_semicolon: + assert(false); + return expraction(); + + case tokentype::tk_singleassign: + return this->on_singleassign(); + + case tokentype::tk_assign: + case tokentype::tk_yields: + + case tokentype::tk_type: + case tokentype::tk_lambda: + case tokentype::tk_if: + case tokentype::tk_let: + + case tokentype::tk_in: + case tokentype::tk_end: + assert(false); + return expraction(); + + case tokentype::tk_invalid: + case tokentype::n_tokentype: + assert(false); + return expraction(); + } + + assert(false); + return expraction(); + } + + expraction + exprstate::on_exprir(const exprir & ir) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + log && log(xtag("ir", ir)); + log && log(xtag("state", *this)); + + switch(this->exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + /* toplevel expression sequence accepts an + * arbitrary number of expressions. + * + * parser::include_token() returns + */ + + if (ir.xir_type() == exprirtype::expression) + return expraction::emit(ir); + + /* NOT IMPLEMENTED */ + assert(false); + return expraction(); + case exprstatetype::def_0: + this->exs_type_ = exprstatetype::def_1; + this->def_lhs_symbol_ = ir.symbol_name(); + + return expraction::keep(); + case exprstatetype::def_1: + /* NOT IMPLEMENTED */ + assert(false); + return expraction(); + case exprstatetype::def_2: + this->exs_type_ = exprstatetype::def_3; + this->def_lhs_type_ = ir.symbol_name(); + + return expraction::keep(); + case exprstatetype::def_3: + /* NOT IMPLEMENTED */ + assert(false); + return expraction(); + case exprstatetype::def_4: + /* have all the ingredients to create an expression + * representing a definition + * + * 1. if ir_type is a symbol, interpret as variable name. + * Need to be able to locate variable by type + * 2. if ir_type is an expression, adopt as rhs + */ + if (ir.xir_type() == exprirtype::expression) { + /* TODO: do something with def_lhs_type */ + + rp rhs_value = ir.expr(); + rp def + = DefineExpr::make(this->def_lhs_symbol_, + rhs_value); + + return expraction(expractiontype::pop, + exprir(exprirtype::expression, def), + exprstatetype::invalid /*not used*/, + exprstatetype::invalid /*not used*/); + } else { + assert(false); + return expraction(); + } + + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_symbol: + /* unreachable + * (this exprstate issues pop instruction from exprstate::on_input() + */ + assert(false); + return expraction(); + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return expraction(); + } + } + + void + exprstate::print(std::ostream & os) const { + os << ""; + } + + // ----- parser ----- + + exprstate & + parser::top_exprstate() { + std::size_t z = stack_.size(); + + if (z == 0) { + throw std::runtime_error + ("parser::top_exprstate: unexpected empty stack"); + } + + return stack_[z-1]; + } + + void + parser::push_exprstate(const exprstate & exs) { + std::size_t z = stack_.size(); + + stack_.resize(z+1); + + stack_[z] = exs; + } + + void + parser::pop_exprstate() { + std::size_t z = stack_.size(); + + if (z > 0) + stack_.resize(z-1); + } + + void + parser::begin_translation_unit() { + this->push_exprstate + (exprstate::expect_toplevel_expression_sequence()); + } + + rp + parser::include_token(const token_type & tk) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + if (stack_.empty()) { + throw std::runtime_error(tostr("parser::include_token", + ": parser not expecting input" + "(call parser.begin_translation_unit()..?)", + xtag("token", tk))); + } + + /* stack_ is non-empty */ + expraction action = this->top_exprstate().on_input(tk); + + /* loop until reach parsing state that requires more input */ + for (;;) { + log && log(xtag("action", action)); + + switch(action.action_type()) { + case expractiontype::keep: + return nullptr; + + case expractiontype::emit: + return action.expr_ir().expr(); + + case expractiontype::pop: + this->pop_exprstate(); + + if (stack_.empty()) { + throw std::runtime_error(tostr("parser::include_token", + ": pop leaves empty stack")); + } + + action = this->top_exprstate().on_exprir(action.expr_ir()); + break; + + case expractiontype::push1: + this->push_exprstate(action.push_exs1()); + return nullptr; + + case expractiontype::push2: + this->push_exprstate(action.push_exs1()); + this->push_exprstate(action.push_exs2()); + return nullptr; + + case expractiontype::invalid: + case expractiontype::n_expractiontype: + /* unreachable */ + assert(false); + return nullptr; + } + } + } /*include_token*/ + + void + parser::print(std::ostream & os) const { + os << "" << std::endl; + } + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end parser.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..d70b7d4d --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,16 @@ +# xo-parser/utest/CMakeLists.txt + +set(UTEST_EXE utest.parser) +set(UTEST_SRCS + parser_utest_main.cpp + parser.test.cpp) + +if (ENABLE_TESTING) + xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS}) + xo_self_dependency(${UTEST_EXE} xo_parser) + #xo_dependency(${UTEST_EXE} xo_ratio) + #xo_dependency(${UTEST_EXE} xo_reflectutil) + xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2) +endif() + +# end CMakeLists.txt diff --git a/utest/parser.test.cpp b/utest/parser.test.cpp new file mode 100644 index 00000000..d5be7db7 --- /dev/null +++ b/utest/parser.test.cpp @@ -0,0 +1,199 @@ +/* file parser.test.cpp + * + * author: Roland Conybeare + */ + +#include "xo/parser/parser.hpp" +#include + +namespace xo { + using parser_type = xo::scm::parser; + using token_type = parser_type::token_type; + using xo::scm::exprstatetype; + using std::cerr; + using std::endl; + + //using xo::ast::Expression; + + namespace ut { + TEST_CASE("parser", "[parser]") { + parser_type parser; + + parser.begin_translation_unit(); + + REQUIRE(parser.stack_size() == 1); + REQUIRE(parser.i_exstype(0) + == exprstatetype::expect_toplevel_expression_sequence); + + /* input: + * def + */ + { + auto r1 = parser.include_token(token_type::def()); + REQUIRE(r1.get() == nullptr); + + /* stack should be: + * + * expect_toplevel_expression_sequence + * def_0 + * expect_symbol + */ + CHECK(parser.stack_size() == 3); + if (parser.stack_size() > 0) + CHECK(parser.i_exstype(0) == exprstatetype::expect_symbol); + if (parser.stack_size() > 1) + CHECK(parser.i_exstype(1) == exprstatetype::def_0); + if (parser.stack_size() > 2) + CHECK(parser.i_exstype(2) + == exprstatetype::expect_toplevel_expression_sequence); + } + + /* input: + * def foo + * ^ ^ + * 0 1 + */ + { + auto r2 = parser.include_token(token_type::symbol_token("foo")); + + cerr << "parser state after [def foo]" << endl; + cerr << parser << endl; + + REQUIRE(r2.get() == nullptr); + + /* stack should be: + * + * expect_toplevel_expression_sequence + * def_1 + */ + CHECK(parser.stack_size() == 2); + if (parser.stack_size() > 0) + CHECK(parser.i_exstype(0) == exprstatetype::def_1); + if (parser.stack_size() > 1) + CHECK(parser.i_exstype(1) + == exprstatetype::expect_toplevel_expression_sequence); + + } + + /* input: + * def foo : + * ^ ^ + * 0 1 + */ + { + auto r3 = parser.include_token(token_type::colon()); + + cerr << "parser state after [def foo :]" << endl; + cerr << parser << endl; + + REQUIRE(r3.get() == nullptr); + + /* stack should be: + * + * expect_toplevel_expression_sequence + * def_2 + * expect_symbol + */ + CHECK(parser.stack_size() == 3); + if (parser.stack_size() > 0) + CHECK(parser.i_exstype(0) == exprstatetype::expect_symbol); + if (parser.stack_size() > 1) + CHECK(parser.i_exstype(1) == exprstatetype::def_2); + if (parser.stack_size() > 2) + CHECK(parser.i_exstype(2) + == exprstatetype::expect_toplevel_expression_sequence); + } + + /* input: + * def foo : footype + * ^ ^ + * 0 1 + */ + { + auto r4 = parser.include_token(token_type::symbol_token("footype")); + + cerr << "parser state after [def foo : footype]" << endl; + cerr << parser << endl; + + REQUIRE(r4.get() == nullptr); + + CHECK(parser.stack_size() == 2); + + /* stack should be: + * + * expect_toplevel_expression_sequence + * def_3 + */ + CHECK(parser.stack_size() == 2); + if (parser.stack_size() > 0) + CHECK(parser.i_exstype(0) == exprstatetype::def_3); + if (parser.stack_size() > 1) + CHECK(parser.i_exstype(1) + == exprstatetype::expect_toplevel_expression_sequence); + + /* expecting either: + * = rhs-expression + * new-expression + */ + } + + /* input: + * def foo : footype = + * ^ ^ + * 0 1 + */ + { + auto r5 = parser.include_token(token_type::singleassign()); + + cerr << "parser state after [def foo : footype =]" << endl; + cerr << parser << endl; + + REQUIRE(r5.get() == nullptr); + + CHECK(parser.stack_size() == 3); + + /* stack should be + * + * expect_toplevel_expression_sequence + * def_4 + * expect_expression + */ + CHECK(parser.stack_size() == 3); + if (parser.stack_size() > 0) + CHECK(parser.i_exstype(0) == exprstatetype::expect_rhs_expression); + if (parser.stack_size() > 1) + CHECK(parser.i_exstype(1) == exprstatetype::def_4); + if (parser.stack_size() > 2) + CHECK(parser.i_exstype(2) + == exprstatetype::expect_toplevel_expression_sequence); + } + + /* input: + * def foo : footype = 3.14159265 + * ^ ^ + * 0 1 + */ + { + auto r6 = parser.include_token(token_type::f64_token("3.14159265")); + + cerr << "parser state after [def foo : footype = 3.14159265]" << endl; + cerr << parser << endl; + + REQUIRE(r6.get() != nullptr); + + CHECK(parser.stack_size() == 1); + + /* stack should be + * + * expect_toplevel_expression_sequence + */ + CHECK(parser.stack_size() == 1); + if (parser.stack_size() > 0) + CHECK(parser.i_exstype(0) + == exprstatetype::expect_toplevel_expression_sequence); + } + } /*TEST_CASE(parser)*/ + } /*namespace ut*/ +} /*namespace xo*/ + +/* end parser.test.cpp */ diff --git a/utest/parser_utest_main.cpp b/utest/parser_utest_main.cpp new file mode 100644 index 00000000..d1013151 --- /dev/null +++ b/utest/parser_utest_main.cpp @@ -0,0 +1,6 @@ +/* file parser_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include + +/* end parser_utest_main.cpp */ From 4132a66165529704b9d4b24c7b27b662b0c71856 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 09:44:52 +1000 Subject: [PATCH 1244/2524] xo-parser: + expect_type + exprir::td_ etc. --- include/xo/parser/parser.hpp | 14 ++++++-- src/parser/parser.cpp | 64 +++++++++++++++++++++++++++++++----- utest/parser.test.cpp | 28 ++++++++-------- 3 files changed, 82 insertions(+), 24 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 92b8e046..e5e87194 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -20,6 +20,7 @@ namespace xo { empty, symbol, expression, + typedescr, n_exprirtype }; @@ -43,6 +44,7 @@ namespace xo { class exprir { public: using Expression = xo::ast::Expression; + using TypeDescr = xo::reflect::TypeDescr; public: exprir() = default; @@ -52,10 +54,14 @@ namespace xo { exprir(exprirtype xir_type, rp expr) : xir_type_{xir_type}, expr_{std::move(expr)} {} + exprir(exprirtype xir_type, + TypeDescr td) + : xir_type_{xir_type}, td_{td} {} exprirtype xir_type() const { return xir_type_; } const std::string & symbol_name() const { return symbol_name_; } const rp & expr() const { return expr_; } + TypeDescr td() const { return td_; } void print(std::ostream & os) const; @@ -66,6 +72,8 @@ namespace xo { std::string symbol_name_; /** xir_type=expression: a completed expression **/ rp expr_; + /** xir_type=typedescr: object identifying/describing a datatype **/ + TypeDescr td_ = nullptr; }; inline std::ostream & @@ -88,6 +96,7 @@ namespace xo { expect_rhs_expression, expect_symbol, + expect_type, n_exprstatetype }; @@ -184,6 +193,7 @@ namespace xo { public: using exprtype = xo::ast::exprtype; using token_type = token; + using TypeDescr = xo::reflect::TypeDescr; public: exprstate() = default; @@ -240,7 +250,7 @@ namespace xo { * | | | | | | (done) * | | | | | def_4:expect_rhs_expression * | | | | def_3 - * | | | def_2:expect_symbol + * | | | def_2:expect_type * | | def_1 * | def_0:expect_symbol * expect_toplevel_expression_sequence @@ -262,7 +272,7 @@ namespace xo { /** e.g. f64 in * def foo : f64 = 1 **/ - std::string def_lhs_type_; + TypeDescr def_lhs_td_ = nullptr; }; /*exprstate*/ inline std::ostream & diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index ab6404fd..9bd6b8f6 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -6,13 +6,15 @@ #include "parser.hpp" #include "xo/expression/DefineExpr.hpp" #include "xo/expression/Constant.hpp" -#include +//#include #include namespace xo { using xo::ast::Expression; using xo::ast::DefineExpr; using xo::ast::Constant; + using xo::reflect::Reflect; + using xo::reflect::TypeDescr; namespace scm { const char * @@ -26,6 +28,8 @@ namespace xo { return "symbol"; case exprirtype::expression: return "expression"; + case exprirtype::typedescr: + return "typedescr"; case exprirtype::n_exprirtype: break; } @@ -38,8 +42,10 @@ namespace xo { os << ""; + << xtag("expr", expr_); + if (td_) + os << xtag("td", td_->short_name()); + os << ">"; } const char * @@ -63,6 +69,8 @@ namespace xo { return "expect_rhs_expression"; case exprstatetype::expect_symbol: return "expect_symbol"; + case exprstatetype::expect_type: + return "expect_type"; case exprstatetype::n_exprstatetype: break; } @@ -147,6 +155,7 @@ namespace xo { case exprstatetype::expect_rhs_expression: return false; case exprstatetype::expect_symbol: + case exprstatetype::expect_type: return false; case exprstatetype::invalid: case exprstatetype::n_exprstatetype: @@ -173,6 +182,10 @@ namespace xo { case exprstatetype::expect_symbol: return true; + case exprstatetype::expect_type: + /* treat symbol as typename */ + return true; + case exprstatetype::invalid: case exprstatetype::n_exprstatetype: /* unreachable */ @@ -198,6 +211,7 @@ namespace xo { * may not begin with a colon */ case exprstatetype::expect_symbol: + case exprstatetype::expect_type: return false; case exprstatetype::invalid: @@ -225,6 +239,7 @@ namespace xo { * may not begin with singleassign '=' */ case exprstatetype::expect_symbol: + case exprstatetype::expect_type: return false; case exprstatetype::invalid: @@ -249,6 +264,7 @@ namespace xo { return true; case exprstatetype::expect_symbol: + case exprstatetype::expect_type: return false; case exprstatetype::invalid: @@ -324,6 +340,36 @@ namespace xo { exprstatetype::invalid /*not used*/, exprstatetype::invalid /*not used*/); + case exprstatetype::expect_type: { + TypeDescr td = nullptr; + + /* TODO: replace with typetable lookup */ + + if (tk.text() == "f64") + td = Reflect::require(); + else if(tk.text() == "f32") + td = Reflect::require(); + else if(tk.text() == "i16") + td = Reflect::require(); + else if(tk.text() == "i32") + td = Reflect::require(); + else if(tk.text() == "i64") + td = Reflect::require(); + + if (!td) { + throw std::runtime_error + (tostr(self_name, + ": unknown type name", + " (expecting f64|f32|i16|i32|i64)", + xtag("typename", tk.text()))); + } + + return expraction(expractiontype::pop, + exprir(exprirtype::typedescr, td), + exprstatetype::invalid /*not used*/, + exprstatetype::invalid /*not used*/); + } + case exprstatetype::invalid: case exprstatetype::n_exprstatetype: /* unreachable */ @@ -352,7 +398,7 @@ namespace xo { return expraction(expractiontype::push1, exprir(), - exprstatetype::expect_symbol, + exprstatetype::expect_type, exprstatetype::invalid /*not used*/); } else { assert(false); @@ -520,7 +566,7 @@ namespace xo { return expraction(); case exprstatetype::def_2: this->exs_type_ = exprstatetype::def_3; - this->def_lhs_type_ = ir.symbol_name(); + this->def_lhs_td_ = ir.td(); return expraction::keep(); case exprstatetype::def_3: @@ -553,6 +599,7 @@ namespace xo { } case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_type: case exprstatetype::expect_symbol: /* unreachable * (this exprstate issues pop instruction from exprstate::on_input() @@ -571,9 +618,10 @@ namespace xo { exprstate::print(std::ostream & os) const { os << ""; + << xtag("def_lhs_symbol", def_lhs_symbol_); + if (def_lhs_td_) + os << xtag("def_lhs_td", def_lhs_td_->short_name()); + os << ">"; } // ----- parser ----- diff --git a/utest/parser.test.cpp b/utest/parser.test.cpp index d5be7db7..e656d9de 100644 --- a/utest/parser.test.cpp +++ b/utest/parser.test.cpp @@ -96,7 +96,7 @@ namespace xo { */ CHECK(parser.stack_size() == 3); if (parser.stack_size() > 0) - CHECK(parser.i_exstype(0) == exprstatetype::expect_symbol); + CHECK(parser.i_exstype(0) == exprstatetype::expect_type); if (parser.stack_size() > 1) CHECK(parser.i_exstype(1) == exprstatetype::def_2); if (parser.stack_size() > 2) @@ -105,14 +105,14 @@ namespace xo { } /* input: - * def foo : footype - * ^ ^ - * 0 1 + * def foo : f64 + * ^ ^ + * 0 1 */ { - auto r4 = parser.include_token(token_type::symbol_token("footype")); + auto r4 = parser.include_token(token_type::symbol_token("f64")); - cerr << "parser state after [def foo : footype]" << endl; + cerr << "parser state after [def foo : f64]" << endl; cerr << parser << endl; REQUIRE(r4.get() == nullptr); @@ -138,14 +138,14 @@ namespace xo { } /* input: - * def foo : footype = - * ^ ^ - * 0 1 + * def foo : f64 = + * ^ ^ + * 0 1 */ { auto r5 = parser.include_token(token_type::singleassign()); - cerr << "parser state after [def foo : footype =]" << endl; + cerr << "parser state after [def foo : f64 =]" << endl; cerr << parser << endl; REQUIRE(r5.get() == nullptr); @@ -169,14 +169,14 @@ namespace xo { } /* input: - * def foo : footype = 3.14159265 - * ^ ^ - * 0 1 + * def foo : f64 = 3.14159265 + * ^ ^ + * 0 1 */ { auto r6 = parser.include_token(token_type::f64_token("3.14159265")); - cerr << "parser state after [def foo : footype = 3.14159265]" << endl; + cerr << "parser state after [def foo : f64 = 3.14159265]" << endl; cerr << parser << endl; REQUIRE(r6.get() != nullptr); From 32b3998094a1242853f2aedc5441b295e30c0883 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 10:04:10 +1000 Subject: [PATCH 1245/2524] xo-parser: use ConvertExpr for 'def foo : sometype...' --- src/parser/parser.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 9bd6b8f6..9851a987 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -6,12 +6,14 @@ #include "parser.hpp" #include "xo/expression/DefineExpr.hpp" #include "xo/expression/Constant.hpp" +#include "xo/expression/ConvertExpr.hpp" //#include #include namespace xo { using xo::ast::Expression; using xo::ast::DefineExpr; + using xo::ast::ConvertExpr; using xo::ast::Constant; using xo::reflect::Reflect; using xo::reflect::TypeDescr; @@ -582,12 +584,15 @@ namespace xo { * 2. if ir_type is an expression, adopt as rhs */ if (ir.xir_type() == exprirtype::expression) { - /* TODO: do something with def_lhs_type */ + /* TODO: do something with def_lhs_td */ rp rhs_value = ir.expr(); - rp def - = DefineExpr::make(this->def_lhs_symbol_, - rhs_value); + + if (def_lhs_td_) + rhs_value = ConvertExpr::make(def_lhs_td_, rhs_value); + + rp def = DefineExpr::make(this->def_lhs_symbol_, + rhs_value); return expraction(expractiontype::pop, exprir(exprirtype::expression, def), From 2f7176b1022bd16eb2833ae6e315cbd5f4466f79 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 10:31:42 +1000 Subject: [PATCH 1246/2524] xo-parser: + expraction::pop() & apply --- include/xo/parser/parser.hpp | 1 + src/parser/parser.cpp | 30 +++++++++++++----------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index e5e87194..11a3a2e2 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -147,6 +147,7 @@ namespace xo { static expraction keep(); static expraction emit(const exprir & ir); static expraction push2(exprstatetype s1, exprstatetype s2); + static expraction pop(const exprir & ir); expractiontype action_type() const { return action_type_; } const exprir & expr_ir() const { return expr_ir_; } diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 9851a987..312b38f0 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -127,6 +127,14 @@ namespace xo { s2); } + expraction + expraction::pop(const exprir & ir) { + return expraction(expractiontype::pop, + ir, + exprstatetype::invalid /*not used*/, + exprstatetype::invalid /*not used*/); + } + void expraction::print(std::ostream & os) const { os << "exs_type_ == exprstatetype::expect_rhs_expression) { - return expraction(expractiontype::pop, - exprir(exprirtype::expression, - Constant::make(tk.f64_value())), - exprstatetype::invalid /*not used*/, - exprstatetype::invalid /*not used*/); + return expraction::pop(exprir(exprirtype::expression, + Constant::make(tk.f64_value()))); } else { assert(false); return expraction(); @@ -594,10 +593,7 @@ namespace xo { rp def = DefineExpr::make(this->def_lhs_symbol_, rhs_value); - return expraction(expractiontype::pop, - exprir(exprirtype::expression, def), - exprstatetype::invalid /*not used*/, - exprstatetype::invalid /*not used*/); + return expraction::pop(exprir(exprirtype::expression, def)); } else { assert(false); return expraction(); From a5bd857efdd17a434823e9df854e55551d176ce4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 10:32:23 +1000 Subject: [PATCH 1247/2524] xo-parser: refactor: xtract exprstatestack from parser --- include/xo/parser/parser.hpp | 52 +++++++++++++++++++++++++++++------- src/parser/parser.cpp | 52 +++++++++++++++++++++++------------- 2 files changed, 76 insertions(+), 28 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 11a3a2e2..b81b5ff3 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -282,6 +282,45 @@ namespace xo { return os; } + /** @class exprstatestack + * @brief A stack of exprstate objects + **/ + class exprstatestack { + public: + exprstatestack() {} + + bool empty() const { return stack_.empty(); } + std::size_t size() const { return stack_.size(); } + + exprstate & top_exprstate(); + void push_exprstate(const exprstate & exs); + void pop_exprstate(); + + /** relative to top-of-stack. + * 0 -> top (last in), z-1 -> bottom (first in) + **/ + exprstate & operator[](std::size_t i) { + std::size_t z = stack_.size(); + + assert(i < z); + + return stack_[z - i - 1]; + } + + const exprstate & operator[](std::size_t i) const { + std::size_t z = stack_.size(); + + assert(i < z); + + return stack_[z - i - 1]; + } + + void print (std::ostream & os) const; + + private: + std::vector stack_; + }; + /** schematica parser * * Examples: @@ -400,17 +439,17 @@ namespace xo { parser() = default; /** for diagnostics: number of entries in parser stack **/ - std::size_t stack_size() const { return stack_.size(); } + std::size_t stack_size() const { return xs_stack_.size(); } /** for diagnostics: exprstatetype at level @p i * (taken relative to top of stack) * * @pre 0 <= i < stack_size **/ exprstatetype i_exstype(std::size_t i) const { - std::size_t z = stack_.size(); + std::size_t z = xs_stack_.size(); if (i < z) { - return stack_[(z - 1) - i].exs_type(); + return xs_stack_[i].exs_type(); } /* out of bounds */ @@ -433,11 +472,6 @@ namespace xo { /** print human-readable representation on stream @p os **/ void print(std::ostream & os) const; - private: - exprstate & top_exprstate(); - void push_exprstate(const exprstate & exs); - void pop_exprstate(); - private: /** state recording state associated with enclosing expressions. * @@ -448,7 +482,7 @@ namespace xo { * - bottom of stack is stack_[0] * - top of stack is stack_[N-1] **/ - std::vector stack_; + exprstatestack xs_stack_; }; /*parser*/ diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 312b38f0..a4f93b70 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -625,10 +625,10 @@ namespace xo { os << ">"; } - // ----- parser ----- + // ----- exprstatestack ----- exprstate & - parser::top_exprstate() { + exprstatestack::top_exprstate() { std::size_t z = stack_.size(); if (z == 0) { @@ -640,7 +640,7 @@ namespace xo { } void - parser::push_exprstate(const exprstate & exs) { + exprstatestack::push_exprstate(const exprstate & exs) { std::size_t z = stack_.size(); stack_.resize(z+1); @@ -649,16 +649,33 @@ namespace xo { } void - parser::pop_exprstate() { + exprstatestack::pop_exprstate() { std::size_t z = stack_.size(); if (z > 0) stack_.resize(z-1); } + void + exprstatestack::print(std::ostream & os) const { + os << "" << std::endl; + } + + // ----- parser ----- + void parser::begin_translation_unit() { - this->push_exprstate + xs_stack_.push_exprstate (exprstate::expect_toplevel_expression_sequence()); } @@ -668,7 +685,7 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - if (stack_.empty()) { + if (xs_stack_.empty()) { throw std::runtime_error(tostr("parser::include_token", ": parser not expecting input" "(call parser.begin_translation_unit()..?)", @@ -676,7 +693,7 @@ namespace xo { } /* stack_ is non-empty */ - expraction action = this->top_exprstate().on_input(tk); + expraction action = xs_stack_.top_exprstate().on_input(tk); /* loop until reach parsing state that requires more input */ for (;;) { @@ -690,23 +707,25 @@ namespace xo { return action.expr_ir().expr(); case expractiontype::pop: - this->pop_exprstate(); + xs_stack_.pop_exprstate(); - if (stack_.empty()) { + if (xs_stack_.empty()) { throw std::runtime_error(tostr("parser::include_token", ": pop leaves empty stack")); } - action = this->top_exprstate().on_exprir(action.expr_ir()); + action = (xs_stack_ + .top_exprstate() + .on_exprir(action.expr_ir())); break; case expractiontype::push1: - this->push_exprstate(action.push_exs1()); + xs_stack_.push_exprstate(action.push_exs1()); return nullptr; case expractiontype::push2: - this->push_exprstate(action.push_exs1()); - this->push_exprstate(action.push_exs2()); + xs_stack_.push_exprstate(action.push_exs1()); + xs_stack_.push_exprstate(action.push_exs2()); return nullptr; case expractiontype::invalid: @@ -721,14 +740,9 @@ namespace xo { void parser::print(std::ostream & os) const { os << "" << std::endl; } From 6affaf9dedaec5b550c9e5f7728160fc7872761e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 10:37:54 +1000 Subject: [PATCH 1248/2524] xo-parser: refactor: + xs_stack arg to exprstate input methods --- include/xo/parser/parser.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index b81b5ff3..287d8256 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -188,6 +188,8 @@ namespace xo { return os; } + class exprstatestack; + /** state associated with a partially-parsed expression. **/ class exprstate { @@ -228,11 +230,11 @@ namespace xo { /** update exprstate in response to incoming token @p tk, * forward instructions to parent parser **/ - expraction on_input(const token_type & tk); + expraction on_input(const token_type & tk, exprstatestack * p_stack); /** update exprstate in response to IR (intermediate representation) * from nested parsing task **/ - expraction on_exprir(const exprir & ir); + expraction on_exprir(const exprir & ir, exprstatestack * p_stack); /** print human-readable representation on @p os **/ void print(std::ostream & os) const; From 847f8744b34c7cd7fcde931512c0d1d1569a1ab0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 10:41:55 +1000 Subject: [PATCH 1249/2524] xo-parser: refactor: explicit stack eliminates push2 actiontype --- include/xo/parser/parser.hpp | 5 +++-- src/parser/parser.cpp | 35 ++++++++++++++++++++++------------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 287d8256..43f80d39 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -114,7 +114,6 @@ namespace xo { invalid = -1, push1, - push2, keep, emit, pop, @@ -146,7 +145,9 @@ namespace xo { static expraction keep(); static expraction emit(const exprir & ir); +#ifdef OBSOLETE static expraction push2(exprstatetype s1, exprstatetype s2); +#endif static expraction pop(const exprir & ir); expractiontype action_type() const { return action_type_; } @@ -240,7 +241,7 @@ namespace xo { void print(std::ostream & os) const; private: - expraction on_def(); + expraction on_def(exprstatestack * p_stack); expraction on_symbol(const token_type & tk); expraction on_colon(); expraction on_singleassign(); diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index a4f93b70..56f06f20 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -87,8 +87,6 @@ namespace xo { return "?invalid"; case expractiontype::push1: return "push1"; - case expractiontype::push2: - return "push2"; case expractiontype::keep: return "keep"; case expractiontype::emit: @@ -118,6 +116,7 @@ namespace xo { exprstatetype::invalid /*not used*/); } +#ifdef OBSOLETE expraction expraction::push2(exprstatetype s1, exprstatetype s2) { @@ -126,6 +125,7 @@ namespace xo { s1, s2); } +#endif expraction expraction::pop(const exprir & ir) { @@ -285,7 +285,7 @@ namespace xo { } expraction - exprstate::on_def() { + exprstate::on_def(exprstatestack * p_stack) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -299,15 +299,17 @@ namespace xo { xtag("state", *this))); } + p_stack->push_exprstate(exprstatetype::def_0); + /* todo: replace: + * expect_symbol_or_function_signature() + */ + p_stack->push_exprstate(exprstatetype::expect_symbol); + /* keyword 'def' introduces a definition: * def pi : f64 = 3.14159265 * def sq(x : f64) -> f64 { (x * x) } */ - return expraction::push2(exprstatetype::def_0, - /* todo: replace: - * expect_symbol_or_function_signature() - */ - exprstatetype::expect_symbol); + return expraction::keep(); } expraction @@ -461,7 +463,9 @@ namespace xo { } expraction - exprstate::on_input(const token_type & tk) { + exprstate::on_input(const token_type & tk, + exprstatestack * p_stack) + { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); log && log(xtag("tk", tk)); @@ -470,7 +474,7 @@ namespace xo { switch(tk.tk_type()) { case tokentype::tk_def: - return this->on_def(); + return this->on_def(p_stack); case tokentype::tk_i64: assert(false); @@ -536,7 +540,9 @@ namespace xo { } expraction - exprstate::on_exprir(const exprir & ir) { + exprstate::on_exprir(const exprir & ir, + exprstatestack * /*p_stack*/) + { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); log && log(xtag("ir", ir)); @@ -693,7 +699,7 @@ namespace xo { } /* stack_ is non-empty */ - expraction action = xs_stack_.top_exprstate().on_input(tk); + expraction action = xs_stack_.top_exprstate().on_input(tk, &xs_stack_); /* loop until reach parsing state that requires more input */ for (;;) { @@ -716,17 +722,20 @@ namespace xo { action = (xs_stack_ .top_exprstate() - .on_exprir(action.expr_ir())); + .on_exprir(action.expr_ir(), + &xs_stack_)); break; case expractiontype::push1: xs_stack_.push_exprstate(action.push_exs1()); return nullptr; +#ifdef OBSOLETE case expractiontype::push2: xs_stack_.push_exprstate(action.push_exs1()); xs_stack_.push_exprstate(action.push_exs2()); return nullptr; +#endif case expractiontype::invalid: case expractiontype::n_expractiontype: From 00eaa55cc9bf2b46757a1d9d4aae671aeafdcb45 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 10:43:33 +1000 Subject: [PATCH 1250/2524] xo-parser: pref: + exprstatestack arg to exprstate::on_symbol() --- include/xo/parser/parser.hpp | 2 +- src/parser/parser.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 43f80d39..ba903311 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -242,7 +242,7 @@ namespace xo { private: expraction on_def(exprstatestack * p_stack); - expraction on_symbol(const token_type & tk); + expraction on_symbol(const token_type & tk, exprstatestack * p_stack); expraction on_colon(); expraction on_singleassign(); expraction on_f64(const token_type & tk); diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 56f06f20..0bf2c8a6 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -313,7 +313,9 @@ namespace xo { } expraction - exprstate::on_symbol(const token_type & tk) { + exprstate::on_symbol(const token_type & tk, + exprstatestack * /*p_stack*/) + { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -488,7 +490,7 @@ namespace xo { return expraction(); case tokentype::tk_symbol: - return this->on_symbol(tk); + return this->on_symbol(tk, p_stack); case tokentype::tk_leftparen: From cdd40a20c78872f966650c191e5c2e6445db3e8f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 10:44:59 +1000 Subject: [PATCH 1251/2524] xo-parser: prep: + exprstatestack arg to exprstate::on_colon() --- include/xo/parser/parser.hpp | 2 +- src/parser/parser.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index ba903311..6b474ff5 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -243,7 +243,7 @@ namespace xo { private: expraction on_def(exprstatestack * p_stack); expraction on_symbol(const token_type & tk, exprstatestack * p_stack); - expraction on_colon(); + expraction on_colon(exprstatestack * p_stack); expraction on_singleassign(); expraction on_f64(const token_type & tk); diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 0bf2c8a6..0b1436cd 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -387,7 +387,7 @@ namespace xo { } expraction - exprstate::on_colon() { + exprstate::on_colon(exprstatestack * /*p_stack*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -508,7 +508,7 @@ namespace xo { return expraction(); case tokentype::tk_colon: - return this->on_colon(); + return this->on_colon(p_stack); case tokentype::tk_doublecolon: case tokentype::tk_semicolon: From 59837c47f0f355f18c54c10aaec5a61557161779 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 10:45:52 +1000 Subject: [PATCH 1252/2524] xo-parser: prep: + exprstatestack arg to exprstate::on_singleassign() --- include/xo/parser/parser.hpp | 2 +- src/parser/parser.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 6b474ff5..39d768d8 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -244,7 +244,7 @@ namespace xo { expraction on_def(exprstatestack * p_stack); expraction on_symbol(const token_type & tk, exprstatestack * p_stack); expraction on_colon(exprstatestack * p_stack); - expraction on_singleassign(); + expraction on_singleassign(exprstatestack * p_stack); expraction on_f64(const token_type & tk); private: diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 0b1436cd..7302bd3b 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -415,7 +415,7 @@ namespace xo { } expraction - exprstate::on_singleassign() { + exprstate::on_singleassign(exprstatestack * /*p_stack*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -516,7 +516,7 @@ namespace xo { return expraction(); case tokentype::tk_singleassign: - return this->on_singleassign(); + return this->on_singleassign(p_stack); case tokentype::tk_assign: case tokentype::tk_yields: From 6b53afe6e66bb9f98b6276b31fce80167d07140e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 10:47:07 +1000 Subject: [PATCH 1253/2524] xo-parser: prep: + exprstatestack arg to exprstate::on_f64() --- include/xo/parser/parser.hpp | 2 +- src/parser/parser.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 39d768d8..a85f3a16 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -245,7 +245,7 @@ namespace xo { expraction on_symbol(const token_type & tk, exprstatestack * p_stack); expraction on_colon(exprstatestack * p_stack); expraction on_singleassign(exprstatestack * p_stack); - expraction on_f64(const token_type & tk); + expraction on_f64(const token_type & tk, exprstatestack * p_stack); private: /** diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 7302bd3b..0bbe0167 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -442,7 +442,9 @@ namespace xo { } expraction - exprstate::on_f64(const token_type & tk) { + exprstate::on_f64(const token_type & tk, + exprstatestack * /*p_stack*/) + { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -483,7 +485,7 @@ namespace xo { return expraction(); case tokentype::tk_f64: - return this->on_f64(tk); + return this->on_f64(tk, p_stack); case tokentype::tk_string: assert(false); From 8bc91d3b830a4f2d35d6781459e6c4464bd2cd38 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 10:50:14 +1000 Subject: [PATCH 1254/2524] xo-parser: refactor: explicit stack eliminates expractiontype::push1 --- include/xo/parser/parser.hpp | 1 - src/parser/parser.cpp | 22 +++++++++------------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index a85f3a16..8e878099 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -113,7 +113,6 @@ namespace xo { enum class expractiontype { invalid = -1, - push1, keep, emit, pop, diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 0bbe0167..c01cda37 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -85,8 +85,6 @@ namespace xo { switch(x) { case expractiontype::invalid: return "?invalid"; - case expractiontype::push1: - return "push1"; case expractiontype::keep: return "keep"; case expractiontype::emit: @@ -387,7 +385,7 @@ namespace xo { } expraction - exprstate::on_colon(exprstatestack * /*p_stack*/) { + exprstate::on_colon(exprstatestack * p_stack) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -404,10 +402,9 @@ namespace xo { if (this->exs_type_ == exprstatetype::def_1) { this->exs_type_ = exprstatetype::def_2; - return expraction(expractiontype::push1, - exprir(), - exprstatetype::expect_type, - exprstatetype::invalid /*not used*/); + p_stack->push_exprstate(exprstatetype::expect_type); + + return expraction::keep(); } else { assert(false); return expraction(); @@ -415,7 +412,7 @@ namespace xo { } expraction - exprstate::on_singleassign(exprstatestack * /*p_stack*/) { + exprstate::on_singleassign(exprstatestack * p_stack) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -431,10 +428,9 @@ namespace xo { if (this->exs_type_ == exprstatetype::def_3) { this->exs_type_ = exprstatetype::def_4; - return expraction(expractiontype::push1, - exprir(), - exprstatetype::expect_rhs_expression, - exprstatetype::invalid /*not used*/); + p_stack->push_exprstate(exprstatetype::expect_rhs_expression); + + return expraction::keep(); } else { assert(false); return expraction(); @@ -730,11 +726,11 @@ namespace xo { &xs_stack_)); break; +#ifdef OBSOLETE case expractiontype::push1: xs_stack_.push_exprstate(action.push_exs1()); return nullptr; -#ifdef OBSOLETE case expractiontype::push2: xs_stack_.push_exprstate(action.push_exs1()); xs_stack_.push_exprstate(action.push_exs2()); From c7c6bc888af142012a99e572ac6f6af5f3a9409c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 10:52:41 +1000 Subject: [PATCH 1255/2524] xo-parser: simplify: drop expraction:: push_exs1, push_exs2 --- include/xo/parser/parser.hpp | 21 ++------------------- src/parser/parser.cpp | 25 +++---------------------- 2 files changed, 5 insertions(+), 41 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 8e878099..838b4cec 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -135,24 +135,16 @@ namespace xo { public: expraction() = default; expraction(expractiontype action_type, - const exprir & expr_ir, - exprstatetype push_exs1, - exprstatetype push_exs2) - : action_type_{action_type}, expr_ir_{expr_ir}, - push_exs1_{push_exs1}, push_exs2_{push_exs2} + const exprir & expr_ir) + : action_type_{action_type}, expr_ir_{expr_ir} {} static expraction keep(); static expraction emit(const exprir & ir); -#ifdef OBSOLETE - static expraction push2(exprstatetype s1, exprstatetype s2); -#endif static expraction pop(const exprir & ir); expractiontype action_type() const { return action_type_; } const exprir & expr_ir() const { return expr_ir_; } - exprstatetype push_exs1() const { return push_exs1_; } - exprstatetype push_exs2() const { return push_exs2_; } void print(std::ostream & os) const; @@ -169,15 +161,6 @@ namespace xo { * intermediate representation (pass to enclosing stack state) **/ exprir expr_ir_; - /** with action_type push1 or push2, - * parser will push exprstate with this type - **/ - exprstatetype push_exs1_ = exprstatetype::invalid; - /** with action_type push2, - * parser will push exprstate with this type - * (after pushing exprstate built from push_exs1_) - **/ - exprstatetype push_exs2_ = exprstatetype::invalid; }; inline std::ostream & diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index c01cda37..ea5c372a 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -101,36 +101,19 @@ namespace xo { expraction expraction::keep() { return expraction(expractiontype::keep, - exprir(), - exprstatetype::invalid /*not used*/, - exprstatetype::invalid /*not used*/); + exprir()); } expraction expraction::emit(const exprir & ir) { return expraction(expractiontype::emit, - ir, - exprstatetype::invalid /*not used*/, - exprstatetype::invalid /*not used*/); + ir); } -#ifdef OBSOLETE - expraction - expraction::push2(exprstatetype s1, - exprstatetype s2) { - return expraction(expractiontype::push2, - exprir(), - s1, - s2); - } -#endif - expraction expraction::pop(const exprir & ir) { return expraction(expractiontype::pop, - ir, - exprstatetype::invalid /*not used*/, - exprstatetype::invalid /*not used*/); + ir); } void @@ -138,8 +121,6 @@ namespace xo { os << ""; } From 010f15641e1507d797391d80a5c719db4ab7b488 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 12:45:20 +1000 Subject: [PATCH 1256/2524] xo-parser: refactor: bypass exprir arg to pop exprstateaction --- include/xo/parser/parser.hpp | 7 ++--- src/parser/parser.cpp | 50 ++++++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 838b4cec..fc735bcd 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -134,14 +134,15 @@ namespace xo { class expraction { public: expraction() = default; - expraction(expractiontype action_type, - const exprir & expr_ir) + explicit expraction(expractiontype action_type, + const exprir & expr_ir) : action_type_{action_type}, expr_ir_{expr_ir} {} static expraction keep(); static expraction emit(const exprir & ir); - static expraction pop(const exprir & ir); + //static expraction pop(const exprir & ir); + static expraction pop(); expractiontype action_type() const { return action_type_; } const exprir & expr_ir() const { return expr_ir_; } diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index ea5c372a..b4d6ae45 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -110,11 +110,19 @@ namespace xo { ir); } +#ifdef OBSOLETE expraction expraction::pop(const exprir & ir) { return expraction(expractiontype::pop, ir); } +#endif + + expraction + expraction::pop() { + return expraction(expractiontype::pop, + exprir()); + } void expraction::print(std::ostream & os) const { @@ -293,7 +301,7 @@ namespace xo { expraction exprstate::on_symbol(const token_type & tk, - exprstatestack * /*p_stack*/) + exprstatestack * p_stack) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -328,7 +336,12 @@ namespace xo { case exprstatetype::expect_rhs_expression: case exprstatetype::expect_symbol: - return expraction::pop(exprir(exprirtype::symbol, tk.text())); + /* have to do pop first */ + + p_stack->pop_exprstate(); + return p_stack->top_exprstate().on_exprir + (exprir(exprirtype::symbol, tk.text()), p_stack); + //return expraction::pop(exprir(exprirtype::symbol, tk.text())); case exprstatetype::expect_type: { TypeDescr td = nullptr; @@ -354,7 +367,10 @@ namespace xo { xtag("typename", tk.text()))); } - return expraction::pop(exprir(exprirtype::typedescr, td)); + p_stack->pop_exprstate(); + return p_stack->top_exprstate().on_exprir + (exprir(exprirtype::typedescr, td), p_stack); + //return expraction::pop(exprir(exprirtype::typedescr, td)); } case exprstatetype::invalid: @@ -420,7 +436,7 @@ namespace xo { expraction exprstate::on_f64(const token_type & tk, - exprstatestack * /*p_stack*/) + exprstatestack * p_stack) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -435,8 +451,12 @@ namespace xo { } if (this->exs_type_ == exprstatetype::expect_rhs_expression) { - return expraction::pop(exprir(exprirtype::expression, - Constant::make(tk.f64_value()))); + p_stack->pop_exprstate(); + + return p_stack->top_exprstate() + .on_exprir(exprir(exprirtype::expression, + Constant::make(tk.f64_value())), + p_stack); } else { assert(false); return expraction(); @@ -522,7 +542,7 @@ namespace xo { expraction exprstate::on_exprir(const exprir & ir, - exprstatestack * /*p_stack*/) + exprstatestack * p_stack) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -580,7 +600,10 @@ namespace xo { rp def = DefineExpr::make(this->def_lhs_symbol_, rhs_value); - return expraction::pop(exprir(exprirtype::expression, def)); + p_stack->pop_exprstate(); + return p_stack->top_exprstate() + .on_exprir(exprir(exprirtype::expression, def), + p_stack); } else { assert(false); return expraction(); @@ -707,17 +730,6 @@ namespace xo { &xs_stack_)); break; -#ifdef OBSOLETE - case expractiontype::push1: - xs_stack_.push_exprstate(action.push_exs1()); - return nullptr; - - case expractiontype::push2: - xs_stack_.push_exprstate(action.push_exs1()); - xs_stack_.push_exprstate(action.push_exs2()); - return nullptr; -#endif - case expractiontype::invalid: case expractiontype::n_expractiontype: /* unreachable */ From 942de7335c0f6dc0fd3249c2aa7df06d101d4588 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 12:48:01 +1000 Subject: [PATCH 1257/2524] xo-parser: refactor: drop unused expractiontype::pop --- include/xo/parser/parser.hpp | 5 +++-- src/parser/parser.cpp | 8 ++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index fc735bcd..ce328bbd 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -115,7 +115,7 @@ namespace xo { keep, emit, - pop, + //pop, n_expractiontype }; @@ -141,8 +141,9 @@ namespace xo { static expraction keep(); static expraction emit(const exprir & ir); - //static expraction pop(const exprir & ir); +#ifdef OBSOLETE static expraction pop(); +#endif expractiontype action_type() const { return action_type_; } const exprir & expr_ir() const { return expr_ir_; } diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index b4d6ae45..b770218b 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -89,8 +89,8 @@ namespace xo { return "keep"; case expractiontype::emit: return "emit"; - case expractiontype::pop: - return "pop"; + //case expractiontype::pop: + //return "pop"; case expractiontype::n_expractiontype: break; } @@ -118,11 +118,13 @@ namespace xo { } #endif +#ifdef OBSOLETE expraction expraction::pop() { return expraction(expractiontype::pop, exprir()); } +#endif void expraction::print(std::ostream & os) const { @@ -716,6 +718,7 @@ namespace xo { case expractiontype::emit: return action.expr_ir().expr(); +#ifdef OBSOLETE case expractiontype::pop: xs_stack_.pop_exprstate(); @@ -729,6 +732,7 @@ namespace xo { .on_exprir(action.expr_ir(), &xs_stack_)); break; +#endif case expractiontype::invalid: case expractiontype::n_expractiontype: From 03f11ab70a59e57d10853c771daa22c3d910a32b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 12:55:43 +1000 Subject: [PATCH 1258/2524] xo-parser: progress: + arg to rcv parsed expr -> simplify exprir --- include/xo/parser/parser.hpp | 13 ++++-- src/parser/parser.cpp | 77 +++++++++++++++++------------------- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index ce328bbd..193d1cc5 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -179,6 +179,7 @@ namespace xo { **/ class exprstate { public: + using Expression = xo::ast::Expression; using exprtype = xo::ast::exprtype; using token_type = token; using TypeDescr = xo::reflect::TypeDescr; @@ -215,21 +216,25 @@ namespace xo { /** update exprstate in response to incoming token @p tk, * forward instructions to parent parser **/ - expraction on_input(const token_type & tk, exprstatestack * p_stack); + expraction on_input(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); /** update exprstate in response to IR (intermediate representation) * from nested parsing task **/ - expraction on_exprir(const exprir & ir, exprstatestack * p_stack); + expraction on_exprir(const exprir & ir, exprstatestack * p_stack, rp * p_emit_expr); /** print human-readable representation on @p os **/ void print(std::ostream & os) const; private: expraction on_def(exprstatestack * p_stack); - expraction on_symbol(const token_type & tk, exprstatestack * p_stack); + expraction on_symbol(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); expraction on_colon(exprstatestack * p_stack); expraction on_singleassign(exprstatestack * p_stack); - expraction on_f64(const token_type & tk, exprstatestack * p_stack); + expraction on_f64(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); private: /** diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index b770218b..4a0f37f5 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -303,7 +303,8 @@ namespace xo { expraction exprstate::on_symbol(const token_type & tk, - exprstatestack * p_stack) + exprstatestack * p_stack, + rp * p_emit_expr) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -342,7 +343,7 @@ namespace xo { p_stack->pop_exprstate(); return p_stack->top_exprstate().on_exprir - (exprir(exprirtype::symbol, tk.text()), p_stack); + (exprir(exprirtype::symbol, tk.text()), p_stack, p_emit_expr); //return expraction::pop(exprir(exprirtype::symbol, tk.text())); case exprstatetype::expect_type: { @@ -371,7 +372,7 @@ namespace xo { p_stack->pop_exprstate(); return p_stack->top_exprstate().on_exprir - (exprir(exprirtype::typedescr, td), p_stack); + (exprir(exprirtype::typedescr, td), p_stack, p_emit_expr); //return expraction::pop(exprir(exprirtype::typedescr, td)); } @@ -438,7 +439,8 @@ namespace xo { expraction exprstate::on_f64(const token_type & tk, - exprstatestack * p_stack) + exprstatestack * p_stack, + rp * p_emit_expr) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -458,7 +460,8 @@ namespace xo { return p_stack->top_exprstate() .on_exprir(exprir(exprirtype::expression, Constant::make(tk.f64_value())), - p_stack); + p_stack, + p_emit_expr); } else { assert(false); return expraction(); @@ -467,7 +470,8 @@ namespace xo { expraction exprstate::on_input(const token_type & tk, - exprstatestack * p_stack) + exprstatestack * p_stack, + rp * p_emit_expr) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -484,14 +488,14 @@ namespace xo { return expraction(); case tokentype::tk_f64: - return this->on_f64(tk, p_stack); + return this->on_f64(tk, p_stack, p_emit_expr); case tokentype::tk_string: assert(false); return expraction(); case tokentype::tk_symbol: - return this->on_symbol(tk, p_stack); + return this->on_symbol(tk, p_stack, p_emit_expr); case tokentype::tk_leftparen: @@ -544,7 +548,8 @@ namespace xo { expraction exprstate::on_exprir(const exprir & ir, - exprstatestack * p_stack) + exprstatestack * p_stack, + rp * p_emit_expr) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -559,8 +564,10 @@ namespace xo { * parser::include_token() returns */ - if (ir.xir_type() == exprirtype::expression) - return expraction::emit(ir); + if (ir.xir_type() == exprirtype::expression) { + *p_emit_expr = ir.expr(); + return expraction::keep(); + } /* NOT IMPLEMENTED */ assert(false); @@ -605,7 +612,7 @@ namespace xo { p_stack->pop_exprstate(); return p_stack->top_exprstate() .on_exprir(exprir(exprirtype::expression, def), - p_stack); + p_stack, p_emit_expr); } else { assert(false); return expraction(); @@ -691,7 +698,7 @@ namespace xo { (exprstate::expect_toplevel_expression_sequence()); } - rp + rp parser::include_token(const token_type & tk) { constexpr bool c_debug_flag = true; @@ -705,42 +712,30 @@ namespace xo { } /* stack_ is non-empty */ - expraction action = xs_stack_.top_exprstate().on_input(tk, &xs_stack_); - /* loop until reach parsing state that requires more input */ - for (;;) { - log && log(xtag("action", action)); + rp retval; - switch(action.action_type()) { - case expractiontype::keep: - return nullptr; + expraction action = xs_stack_.top_exprstate().on_input(tk, &xs_stack_, &retval); - case expractiontype::emit: - return action.expr_ir().expr(); + log && log(xtag("action", action)); + + return retval; #ifdef OBSOLETE - case expractiontype::pop: - xs_stack_.pop_exprstate(); + switch(action.action_type()) { + case expractiontype::keep: + return nullptr; - if (xs_stack_.empty()) { - throw std::runtime_error(tostr("parser::include_token", - ": pop leaves empty stack")); - } + case expractiontype::emit: + //return action.expr_ir().expr(); - action = (xs_stack_ - .top_exprstate() - .on_exprir(action.expr_ir(), - &xs_stack_)); - break; -#endif - - case expractiontype::invalid: - case expractiontype::n_expractiontype: - /* unreachable */ - assert(false); - return nullptr; - } + case expractiontype::invalid: + case expractiontype::n_expractiontype: + /* unreachable */ + assert(false); + return nullptr; } +#endif } /*include_token*/ void From e013082442505f9a1631dd7827264ad417a0b66a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 12:57:42 +1000 Subject: [PATCH 1259/2524] xo-parser: drop expractiontype::emit --- include/xo/parser/parser.hpp | 5 ++--- src/parser/parser.cpp | 20 +------------------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 193d1cc5..bbb0a584 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -114,7 +114,7 @@ namespace xo { invalid = -1, keep, - emit, + //emit, //pop, n_expractiontype @@ -140,9 +140,8 @@ namespace xo { {} static expraction keep(); - static expraction emit(const exprir & ir); #ifdef OBSOLETE - static expraction pop(); + static expraction emit(const exprir & ir); #endif expractiontype action_type() const { return action_type_; } diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 4a0f37f5..3a0eaaf9 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -87,10 +87,6 @@ namespace xo { return "?invalid"; case expractiontype::keep: return "keep"; - case expractiontype::emit: - return "emit"; - //case expractiontype::pop: - //return "pop"; case expractiontype::n_expractiontype: break; } @@ -104,26 +100,12 @@ namespace xo { exprir()); } +#ifdef OBSOLETE expraction expraction::emit(const exprir & ir) { return expraction(expractiontype::emit, ir); } - -#ifdef OBSOLETE - expraction - expraction::pop(const exprir & ir) { - return expraction(expractiontype::pop, - ir); - } -#endif - -#ifdef OBSOLETE - expraction - expraction::pop() { - return expraction(expractiontype::pop, - exprir()); - } #endif void From 04d3961d24ae6bc733f82f5634f39c812ecedaa1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 12:59:52 +1000 Subject: [PATCH 1260/2524] xo-parser: simplify: drop expraction.expr_ir_ --- include/xo/parser/parser.hpp | 19 +++++++++++++------ src/parser/parser.cpp | 12 +----------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index bbb0a584..4fd65ec5 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -134,18 +134,23 @@ namespace xo { class expraction { public: expraction() = default; - explicit expraction(expractiontype action_type, - const exprir & expr_ir) - : action_type_{action_type}, expr_ir_{expr_ir} + explicit expraction(expractiontype action_type +#ifdef OBSOLETE + const exprir & expr_ir +#endif + ) + : action_type_{action_type} +#ifdef OBSOLETE + , expr_ir_{expr_ir} +#endif {} static expraction keep(); -#ifdef OBSOLETE - static expraction emit(const exprir & ir); -#endif expractiontype action_type() const { return action_type_; } +#ifdef OBSOLETE const exprir & expr_ir() const { return expr_ir_; } +#endif void print(std::ostream & os) const; @@ -158,10 +163,12 @@ namespace xo { * pop: drop exprstate, report exprir to parent **/ expractiontype action_type_ = expractiontype::invalid; +#ifdef OBSOLETE /** * intermediate representation (pass to enclosing stack state) **/ exprir expr_ir_; +#endif }; inline std::ostream & diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 3a0eaaf9..ad87f680 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -96,23 +96,13 @@ namespace xo { expraction expraction::keep() { - return expraction(expractiontype::keep, - exprir()); + return expraction(expractiontype::keep); } -#ifdef OBSOLETE - expraction - expraction::emit(const exprir & ir) { - return expraction(expractiontype::emit, - ir); - } -#endif - void expraction::print(std::ostream & os) const { os << ""; } From f1b83ec8053b02e8516c0c89a7761865b5d5ba9b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 13:01:48 +1000 Subject: [PATCH 1261/2524] xo-parser: progress: exprstate::on_input() drop expraction retval --- include/xo/parser/parser.hpp | 2 +- src/parser/parser.cpp | 49 +++++++++++++----------------------- 2 files changed, 19 insertions(+), 32 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 4fd65ec5..e087424f 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -222,7 +222,7 @@ namespace xo { /** update exprstate in response to incoming token @p tk, * forward instructions to parent parser **/ - expraction on_input(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); + void on_input(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); /** update exprstate in response to IR (intermediate representation) * from nested parsing task **/ diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index ad87f680..aac8aa50 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -440,7 +440,7 @@ namespace xo { } } - expraction + void exprstate::on_input(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) @@ -453,21 +453,24 @@ namespace xo { switch(tk.tk_type()) { case tokentype::tk_def: - return this->on_def(p_stack); + this->on_def(p_stack); + return; case tokentype::tk_i64: assert(false); - return expraction(); + return; case tokentype::tk_f64: - return this->on_f64(tk, p_stack, p_emit_expr); + this->on_f64(tk, p_stack, p_emit_expr); + return; case tokentype::tk_string: assert(false); - return expraction(); + return; case tokentype::tk_symbol: - return this->on_symbol(tk, p_stack, p_emit_expr); + this->on_symbol(tk, p_stack, p_emit_expr); + return; case tokentype::tk_leftparen: @@ -482,18 +485,20 @@ namespace xo { case tokentype::tk_dot: case tokentype::tk_comma: assert(false); - return expraction(); + return; case tokentype::tk_colon: - return this->on_colon(p_stack); + this->on_colon(p_stack); + return; case tokentype::tk_doublecolon: case tokentype::tk_semicolon: assert(false); - return expraction(); + return; case tokentype::tk_singleassign: - return this->on_singleassign(p_stack); + this->on_singleassign(p_stack); + return; case tokentype::tk_assign: case tokentype::tk_yields: @@ -506,16 +511,15 @@ namespace xo { case tokentype::tk_in: case tokentype::tk_end: assert(false); - return expraction(); + return; case tokentype::tk_invalid: case tokentype::n_tokentype: assert(false); - return expraction(); + return; } assert(false); - return expraction(); } expraction @@ -687,27 +691,10 @@ namespace xo { rp retval; - expraction action = xs_stack_.top_exprstate().on_input(tk, &xs_stack_, &retval); + xs_stack_.top_exprstate().on_input(tk, &xs_stack_, &retval); - log && log(xtag("action", action)); return retval; - -#ifdef OBSOLETE - switch(action.action_type()) { - case expractiontype::keep: - return nullptr; - - case expractiontype::emit: - //return action.expr_ir().expr(); - - case expractiontype::invalid: - case expractiontype::n_expractiontype: - /* unreachable */ - assert(false); - return nullptr; - } -#endif } /*include_token*/ void From 5281ccb3f50ed4dd74192aab612570abb279964c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 13:04:58 +1000 Subject: [PATCH 1262/2524] xo-parser: simplify: exprstate::on_f64() drop retval --- include/xo/parser/parser.hpp | 6 +++--- src/parser/parser.cpp | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index e087424f..1b349b88 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -238,9 +238,9 @@ namespace xo { rp * p_emit_expr); expraction on_colon(exprstatestack * p_stack); expraction on_singleassign(exprstatestack * p_stack); - expraction on_f64(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); + void on_f64(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); private: /** diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index aac8aa50..e49645f4 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -409,7 +409,7 @@ namespace xo { } } - expraction + void exprstate::on_f64(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) @@ -429,14 +429,13 @@ namespace xo { if (this->exs_type_ == exprstatetype::expect_rhs_expression) { p_stack->pop_exprstate(); - return p_stack->top_exprstate() + p_stack->top_exprstate() .on_exprir(exprir(exprirtype::expression, Constant::make(tk.f64_value())), p_stack, p_emit_expr); } else { assert(false); - return expraction(); } } From 880606908b205585ac2727c171c8ddc53b71effb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 13:05:58 +1000 Subject: [PATCH 1263/2524] xo-parser: simplify: exprstate::on_colon drop retval --- include/xo/parser/parser.hpp | 2 +- src/parser/parser.cpp | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 1b349b88..99117193 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -236,7 +236,7 @@ namespace xo { expraction on_symbol(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); - expraction on_colon(exprstatestack * p_stack); + void on_colon(exprstatestack * p_stack); expraction on_singleassign(exprstatestack * p_stack); void on_f64(const token_type & tk, exprstatestack * p_stack, diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index e49645f4..98baccfc 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -356,7 +356,7 @@ namespace xo { } } - expraction + void exprstate::on_colon(exprstatestack * p_stack) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -375,11 +375,8 @@ namespace xo { this->exs_type_ = exprstatetype::def_2; p_stack->push_exprstate(exprstatetype::expect_type); - - return expraction::keep(); } else { assert(false); - return expraction(); } } From 909101cd8de8b1879e1852c9c24511fae7a68202 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 13:07:33 +1000 Subject: [PATCH 1264/2524] xo-parser: simplify: exprstate::on_singleassign drop retval --- include/xo/parser/parser.hpp | 2 +- src/parser/parser.cpp | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 99117193..4d45f6e7 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -237,7 +237,7 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr); void on_colon(exprstatestack * p_stack); - expraction on_singleassign(exprstatestack * p_stack); + void on_singleassign(exprstatestack * p_stack); void on_f64(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 98baccfc..a1ee3ae2 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -380,7 +380,7 @@ namespace xo { } } - expraction + void exprstate::on_singleassign(exprstatestack * p_stack) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -398,11 +398,8 @@ namespace xo { this->exs_type_ = exprstatetype::def_4; p_stack->push_exprstate(exprstatetype::expect_rhs_expression); - - return expraction::keep(); } else { assert(false); - return expraction(); } } From 129b5d9258aeb12c8d80a4df7d0ee60c47f40e58 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 13:08:54 +1000 Subject: [PATCH 1265/2524] xo-parser: simplify: exprstate::on_def drop retval --- include/xo/parser/parser.hpp | 2 +- src/parser/parser.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 4d45f6e7..a01b1da2 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -232,7 +232,7 @@ namespace xo { void print(std::ostream & os) const; private: - expraction on_def(exprstatestack * p_stack); + void on_def(exprstatestack * p_stack); expraction on_symbol(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index a1ee3ae2..905abfea 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -245,7 +245,7 @@ namespace xo { } } - expraction + void exprstate::on_def(exprstatestack * p_stack) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -270,7 +270,6 @@ namespace xo { * def pi : f64 = 3.14159265 * def sq(x : f64) -> f64 { (x * x) } */ - return expraction::keep(); } expraction From 77ec1c7ead6e58ddd82c97dbc600dd86a7cf4696 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 13:11:27 +1000 Subject: [PATCH 1266/2524] xo-parser: simplify: exprstate::on_symbol drop retval --- include/xo/parser/parser.hpp | 6 +++--- src/parser/parser.cpp | 12 +++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index a01b1da2..b26ad942 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -233,9 +233,9 @@ namespace xo { private: void on_def(exprstatestack * p_stack); - expraction on_symbol(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); + void on_symbol(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); void on_colon(exprstatestack * p_stack); void on_singleassign(exprstatestack * p_stack); void on_f64(const token_type & tk, diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 905abfea..7db6e821 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -272,7 +272,7 @@ namespace xo { */ } - expraction + void exprstate::on_symbol(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) @@ -306,16 +306,17 @@ namespace xo { case exprstatetype::def_4: /* unreachable */ assert(false); - return expraction(); + return; case exprstatetype::expect_rhs_expression: case exprstatetype::expect_symbol: /* have to do pop first */ p_stack->pop_exprstate(); - return p_stack->top_exprstate().on_exprir + p_stack->top_exprstate().on_exprir (exprir(exprirtype::symbol, tk.text()), p_stack, p_emit_expr); //return expraction::pop(exprir(exprirtype::symbol, tk.text())); + return; case exprstatetype::expect_type: { TypeDescr td = nullptr; @@ -342,16 +343,17 @@ namespace xo { } p_stack->pop_exprstate(); - return p_stack->top_exprstate().on_exprir + p_stack->top_exprstate().on_exprir (exprir(exprirtype::typedescr, td), p_stack, p_emit_expr); //return expraction::pop(exprir(exprirtype::typedescr, td)); + return; } case exprstatetype::invalid: case exprstatetype::n_exprstatetype: /* unreachable */ assert(false); - return expraction(); + return; } } From cd83b6bed9be01aa48c7cc5e5b6b30ca04b1cf7b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 13:13:12 +1000 Subject: [PATCH 1267/2524] xo-parser: simplify: exprstate::on_exprir drop retval --- include/xo/parser/parser.hpp | 2 +- src/parser/parser.cpp | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index b26ad942..7056e1ee 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -226,7 +226,7 @@ namespace xo { /** update exprstate in response to IR (intermediate representation) * from nested parsing task **/ - expraction on_exprir(const exprir & ir, exprstatestack * p_stack, rp * p_emit_expr); + void on_exprir(const exprir & ir, exprstatestack * p_stack, rp * p_emit_expr); /** print human-readable representation on @p os **/ void print(std::ostream & os) const; diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 7db6e821..7eb20b29 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -516,7 +516,7 @@ namespace xo { assert(false); } - expraction + void exprstate::on_exprir(const exprir & ir, exprstatestack * p_stack, rp * p_emit_expr) @@ -536,30 +536,30 @@ namespace xo { if (ir.xir_type() == exprirtype::expression) { *p_emit_expr = ir.expr(); - return expraction::keep(); + return; } /* NOT IMPLEMENTED */ assert(false); - return expraction(); + return; case exprstatetype::def_0: this->exs_type_ = exprstatetype::def_1; this->def_lhs_symbol_ = ir.symbol_name(); - return expraction::keep(); + return; case exprstatetype::def_1: /* NOT IMPLEMENTED */ assert(false); - return expraction(); + return; case exprstatetype::def_2: this->exs_type_ = exprstatetype::def_3; this->def_lhs_td_ = ir.td(); - return expraction::keep(); + return; case exprstatetype::def_3: /* NOT IMPLEMENTED */ assert(false); - return expraction(); + return; case exprstatetype::def_4: /* have all the ingredients to create an expression * representing a definition @@ -585,7 +585,7 @@ namespace xo { p_stack, p_emit_expr); } else { assert(false); - return expraction(); + return; } case exprstatetype::expect_rhs_expression: @@ -595,12 +595,12 @@ namespace xo { * (this exprstate issues pop instruction from exprstate::on_input() */ assert(false); - return expraction(); + return; case exprstatetype::invalid: case exprstatetype::n_exprstatetype: /* unreachable */ assert(false); - return expraction(); + return; } } From 514a2cb687b66c9df369c8fde18d328780035b8b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 13:23:16 +1000 Subject: [PATCH 1268/2524] xo-parser: simplify: + exprstate::on_expr split from on_exprir() --- include/xo/parser/parser.hpp | 4 ++ src/parser/parser.cpp | 77 +++++++++++++++++++++++++++++++----- 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 7056e1ee..6e703376 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -223,6 +223,10 @@ namespace xo { * forward instructions to parent parser **/ void on_input(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); + /** update exprstate in response to a successfully-parsed subexpression **/ + void on_expr(ref::brw expr, + exprstatestack * p_stack, + rp * p_emit_expr); /** update exprstate in response to IR (intermediate representation) * from nested parsing task **/ diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 7eb20b29..b3e27701 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -313,8 +313,7 @@ namespace xo { /* have to do pop first */ p_stack->pop_exprstate(); - p_stack->top_exprstate().on_exprir - (exprir(exprirtype::symbol, tk.text()), p_stack, p_emit_expr); + p_stack->top_exprstate().on_exprir(exprir(exprirtype::symbol, tk.text()), p_stack, p_emit_expr); //return expraction::pop(exprir(exprirtype::symbol, tk.text())); return; @@ -424,11 +423,9 @@ namespace xo { if (this->exs_type_ == exprstatetype::expect_rhs_expression) { p_stack->pop_exprstate(); - p_stack->top_exprstate() - .on_exprir(exprir(exprirtype::expression, - Constant::make(tk.f64_value())), - p_stack, - p_emit_expr); + p_stack->top_exprstate().on_expr(Constant::make(tk.f64_value()), + p_stack, + p_emit_expr); } else { assert(false); } @@ -516,6 +513,67 @@ namespace xo { assert(false); } + void + exprstate::on_expr(ref::brw expr, + exprstatestack * p_stack, + rp * p_emit_expr) + { + switch(this->exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + /* toplevel expression sequence accepts an + * arbitrary number of expressions. + * + * parser::include_token() returns + */ + + *p_emit_expr = expr.get(); + return; + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + /* NOT IMPLEMENTED */ + assert(false); + return; + case exprstatetype::def_4: { + /* have all the ingredients to create an expression + * representing a definition + * + * 1. if ir_type is a symbol, interpret as variable name. + * Need to be able to locate variable by type + * 2. if ir_type is an expression, adopt as rhs + */ + rp rhs_value = expr.get(); + + if (def_lhs_td_) + rhs_value = ConvertExpr::make(def_lhs_td_, rhs_value); + + rp def = DefineExpr::make(this->def_lhs_symbol_, + rhs_value); + + p_stack->pop_exprstate(); + p_stack->top_exprstate().on_expr(def, + p_stack, + p_emit_expr); + return; + } + + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_type: + case exprstatetype::expect_symbol: + /* unreachable + * (this exprstate issues pop instruction from exprstate::on_input() + */ + assert(false); + return; + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return; + } + } + void exprstate::on_exprir(const exprir & ir, exprstatestack * p_stack, @@ -580,9 +638,8 @@ namespace xo { rhs_value); p_stack->pop_exprstate(); - return p_stack->top_exprstate() - .on_exprir(exprir(exprirtype::expression, def), - p_stack, p_emit_expr); + return p_stack->top_exprstate().on_expr(def, + p_stack, p_emit_expr); } else { assert(false); return; From 216eea9d2c59338aff782a3a07d068d2e179c94d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 13:26:18 +1000 Subject: [PATCH 1269/2524] xo-parser: simplify: drop exprir.expr --- include/xo/parser/parser.hpp | 7 ------ src/parser/parser.cpp | 41 +++++------------------------------- 2 files changed, 5 insertions(+), 43 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 6e703376..db6ea524 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -19,7 +19,6 @@ namespace xo { empty, symbol, - expression, typedescr, n_exprirtype @@ -51,16 +50,12 @@ namespace xo { exprir(exprirtype xir_type, const std::string & x) : xir_type_{xir_type}, symbol_name_{x} {} - exprir(exprirtype xir_type, - rp expr) - : xir_type_{xir_type}, expr_{std::move(expr)} {} exprir(exprirtype xir_type, TypeDescr td) : xir_type_{xir_type}, td_{td} {} exprirtype xir_type() const { return xir_type_; } const std::string & symbol_name() const { return symbol_name_; } - const rp & expr() const { return expr_; } TypeDescr td() const { return td_; } void print(std::ostream & os) const; @@ -70,8 +65,6 @@ namespace xo { exprirtype xir_type_ = exprirtype::invalid; /** xir_type=symbol: a symbol (type or variable) name **/ std::string symbol_name_; - /** xir_type=expression: a completed expression **/ - rp expr_; /** xir_type=typedescr: object identifying/describing a datatype **/ TypeDescr td_ = nullptr; }; diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index b3e27701..8772e7fd 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -28,8 +28,6 @@ namespace xo { return "empty"; case exprirtype::symbol: return "symbol"; - case exprirtype::expression: - return "expression"; case exprirtype::typedescr: return "typedescr"; case exprirtype::n_exprirtype: @@ -43,8 +41,7 @@ namespace xo { exprir::print(std::ostream & os) const { os << "short_name()); os << ">"; @@ -576,8 +573,8 @@ namespace xo { void exprstate::on_exprir(const exprir & ir, - exprstatestack * p_stack, - rp * p_emit_expr) + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -592,11 +589,6 @@ namespace xo { * parser::include_token() returns */ - if (ir.xir_type() == exprirtype::expression) { - *p_emit_expr = ir.expr(); - return; - } - /* NOT IMPLEMENTED */ assert(false); return; @@ -619,31 +611,8 @@ namespace xo { assert(false); return; case exprstatetype::def_4: - /* have all the ingredients to create an expression - * representing a definition - * - * 1. if ir_type is a symbol, interpret as variable name. - * Need to be able to locate variable by type - * 2. if ir_type is an expression, adopt as rhs - */ - if (ir.xir_type() == exprirtype::expression) { - /* TODO: do something with def_lhs_td */ - - rp rhs_value = ir.expr(); - - if (def_lhs_td_) - rhs_value = ConvertExpr::make(def_lhs_td_, rhs_value); - - rp def = DefineExpr::make(this->def_lhs_symbol_, - rhs_value); - - p_stack->pop_exprstate(); - return p_stack->top_exprstate().on_expr(def, - p_stack, p_emit_expr); - } else { - assert(false); - return; - } + assert(false); + return; case exprstatetype::expect_rhs_expression: case exprstatetype::expect_type: From 44fdba132c3efd0a5b1a157bb7946cb245e77e57 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 13:32:15 +1000 Subject: [PATCH 1270/2524] xo-parser: refactor: exprstate::on_symbol splits symbol from exprir --- include/xo/parser/parser.hpp | 10 ++++++- src/parser/parser.cpp | 54 ++++++++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index db6ea524..57d4b319 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -47,15 +47,17 @@ namespace xo { public: exprir() = default; +#ifdef OBSOLETE exprir(exprirtype xir_type, const std::string & x) : xir_type_{xir_type}, symbol_name_{x} {} +#endif exprir(exprirtype xir_type, TypeDescr td) : xir_type_{xir_type}, td_{td} {} exprirtype xir_type() const { return xir_type_; } - const std::string & symbol_name() const { return symbol_name_; } + //const std::string & symbol_name() const { return symbol_name_; } TypeDescr td() const { return td_; } void print(std::ostream & os) const; @@ -63,8 +65,10 @@ namespace xo { private: /** IR type code **/ exprirtype xir_type_ = exprirtype::invalid; +#ifdef OBSOLETE /** xir_type=symbol: a symbol (type or variable) name **/ std::string symbol_name_; +#endif /** xir_type=typedescr: object identifying/describing a datatype **/ TypeDescr td_ = nullptr; }; @@ -220,6 +224,10 @@ namespace xo { void on_expr(ref::brw expr, exprstatestack * p_stack, rp * p_emit_expr); + /** update exprstate when expecting a symbol **/ + void on_symbol(const std::string & symbol, + exprstatestack * p_stack, + rp * p_emit_expr); /** update exprstate in response to IR (intermediate representation) * from nested parsing task **/ diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 8772e7fd..600c7191 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -40,8 +40,8 @@ namespace xo { void exprir::print(std::ostream & os) const { os << "short_name()); os << ">"; @@ -310,8 +310,8 @@ namespace xo { /* have to do pop first */ p_stack->pop_exprstate(); - p_stack->top_exprstate().on_exprir(exprir(exprirtype::symbol, tk.text()), p_stack, p_emit_expr); - //return expraction::pop(exprir(exprirtype::symbol, tk.text())); + p_stack->top_exprstate().on_symbol(tk.text(), + p_stack, p_emit_expr); return; case exprstatetype::expect_type: { @@ -572,15 +572,10 @@ namespace xo { } void - exprstate::on_exprir(const exprir & ir, + exprstate::on_symbol(const std::string & symbol_name, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - log && log(xtag("ir", ir)); - log && log(xtag("state", *this)); - switch(this->exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: /* toplevel expression sequence accepts an @@ -594,9 +589,46 @@ namespace xo { return; case exprstatetype::def_0: this->exs_type_ = exprstatetype::def_1; - this->def_lhs_symbol_ = ir.symbol_name(); + this->def_lhs_symbol_ = symbol_name; return; + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + /* NOT IMPLEMENTED */ + assert(false); + return; + + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_type: + case exprstatetype::expect_symbol: + /* unreachable + * (this exprstate issues pop instruction from exprstate::on_input() + */ + assert(false); + return; + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return; + } + } + + void + exprstate::on_exprir(const exprir & ir, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + log && log(xtag("ir", ir)); + log && log(xtag("state", *this)); + + switch(this->exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + case exprstatetype::def_0: case exprstatetype::def_1: /* NOT IMPLEMENTED */ assert(false); From 18f32805257f15cee9f1118727303538c2b9c1e7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 13:37:08 +1000 Subject: [PATCH 1271/2524] xo-parser: simplify: drop exprir::td --- include/xo/parser/parser.hpp | 40 +++++------------------- src/parser/parser.cpp | 59 ++++++++++++++++++++++++++---------- 2 files changed, 51 insertions(+), 48 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 57d4b319..a9dbc198 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -47,30 +47,18 @@ namespace xo { public: exprir() = default; -#ifdef OBSOLETE - exprir(exprirtype xir_type, - const std::string & x) - : xir_type_{xir_type}, symbol_name_{x} {} -#endif - exprir(exprirtype xir_type, - TypeDescr td) - : xir_type_{xir_type}, td_{td} {} + explicit exprir(exprirtype xir_type) + : xir_type_{xir_type} {} exprirtype xir_type() const { return xir_type_; } //const std::string & symbol_name() const { return symbol_name_; } - TypeDescr td() const { return td_; } + //TypeDescr td() const { return td_; } void print(std::ostream & os) const; private: /** IR type code **/ exprirtype xir_type_ = exprirtype::invalid; -#ifdef OBSOLETE - /** xir_type=symbol: a symbol (type or variable) name **/ - std::string symbol_name_; -#endif - /** xir_type=typedescr: object identifying/describing a datatype **/ - TypeDescr td_ = nullptr; }; inline std::ostream & @@ -131,23 +119,13 @@ namespace xo { class expraction { public: expraction() = default; - explicit expraction(expractiontype action_type -#ifdef OBSOLETE - const exprir & expr_ir -#endif - ) + explicit expraction(expractiontype action_type) : action_type_{action_type} -#ifdef OBSOLETE - , expr_ir_{expr_ir} -#endif {} static expraction keep(); expractiontype action_type() const { return action_type_; } -#ifdef OBSOLETE - const exprir & expr_ir() const { return expr_ir_; } -#endif void print(std::ostream & os) const; @@ -160,12 +138,6 @@ namespace xo { * pop: drop exprstate, report exprir to parent **/ expractiontype action_type_ = expractiontype::invalid; -#ifdef OBSOLETE - /** - * intermediate representation (pass to enclosing stack state) - **/ - exprir expr_ir_; -#endif }; inline std::ostream & @@ -228,6 +200,10 @@ namespace xo { void on_symbol(const std::string & symbol, exprstatestack * p_stack, rp * p_emit_expr); + /** update exprstate when expeccting a typedescr **/ + void on_typedescr(TypeDescr td, + exprstatestack * p_stack, + rp * p_emit_expr); /** update exprstate in response to IR (intermediate representation) * from nested parsing task **/ diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 600c7191..9eaba07c 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -41,9 +41,6 @@ namespace xo { exprir::print(std::ostream & os) const { os << "short_name()); os << ">"; } @@ -339,9 +336,7 @@ namespace xo { } p_stack->pop_exprstate(); - p_stack->top_exprstate().on_exprir - (exprir(exprirtype::typedescr, td), p_stack, p_emit_expr); - //return expraction::pop(exprir(exprirtype::typedescr, td)); + p_stack->top_exprstate().on_typedescr(td, p_stack, p_emit_expr); return; } @@ -353,6 +348,47 @@ namespace xo { } } + void + exprstate::on_typedescr(TypeDescr td, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + switch(this->exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + case exprstatetype::def_0: + case exprstatetype::def_1: + /* NOT IMPLEMENTED */ + assert(false); + return; + + case exprstatetype::def_2: + this->exs_type_ = exprstatetype::def_3; + this->def_lhs_td_ = td; + + return; + case exprstatetype::def_3: + case exprstatetype::def_4: + /* NOT IMPLEMENTED */ + assert(false); + return; + + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_type: + case exprstatetype::expect_symbol: + /* unreachable + * (this exprstate issues pop instruction from exprstate::on_input() + */ + assert(false); + return; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return; + } + } + void exprstate::on_colon(exprstatestack * p_stack) { constexpr bool c_debug_flag = true; @@ -630,19 +666,10 @@ namespace xo { case exprstatetype::expect_toplevel_expression_sequence: case exprstatetype::def_0: case exprstatetype::def_1: - /* NOT IMPLEMENTED */ - assert(false); - return; case exprstatetype::def_2: - this->exs_type_ = exprstatetype::def_3; - this->def_lhs_td_ = ir.td(); - - return; case exprstatetype::def_3: - /* NOT IMPLEMENTED */ - assert(false); - return; case exprstatetype::def_4: + /* NOT IMPLEMENTED */ assert(false); return; From 325d1f65476358116a16b7329ef02749c3a78e4b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 13:38:56 +1000 Subject: [PATCH 1272/2524] xo-parser: simplify: drop unused exprir type --- include/xo/parser/parser.hpp | 4 ++++ src/parser/parser.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index a9dbc198..deafa708 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -12,6 +12,7 @@ namespace xo { namespace scm { +#ifdef OBSOLETE // ----- exprir ----- enum class exprirtype { @@ -66,6 +67,7 @@ namespace xo { x.print(os); return os; } +#endif enum class exprstatetype { invalid = -1, @@ -204,10 +206,12 @@ namespace xo { void on_typedescr(TypeDescr td, exprstatestack * p_stack, rp * p_emit_expr); +#ifdef OBSOLETE /** update exprstate in response to IR (intermediate representation) * from nested parsing task **/ void on_exprir(const exprir & ir, exprstatestack * p_stack, rp * p_emit_expr); +#endif /** print human-readable representation on @p os **/ void print(std::ostream & os) const; diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 9eaba07c..0fbe7946 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -19,6 +19,7 @@ namespace xo { using xo::reflect::TypeDescr; namespace scm { +#ifdef OBSOLETE const char * exprirtype_descr(exprirtype x) { switch(x) { @@ -43,6 +44,7 @@ namespace xo { << xtag("type", xir_type_); os << ">"; } +#endif const char * exprstatetype_descr(exprstatetype x) { @@ -652,6 +654,7 @@ namespace xo { } } +#ifdef OBSOLETE void exprstate::on_exprir(const exprir & ir, exprstatestack * /*p_stack*/, @@ -688,6 +691,7 @@ namespace xo { return; } } +#endif void exprstate::print(std::ostream & os) const { From 992de108ca2c26cf6a3cdfc2e74bb661f4bf1222 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 13:40:09 +1000 Subject: [PATCH 1273/2524] xo-parser: simplify: drop unused expraction --- include/xo/parser/parser.hpp | 2 ++ src/parser/parser.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index deafa708..ebf2f7d6 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -97,6 +97,7 @@ namespace xo { return os; } +#ifdef OBSOLETE enum class expractiontype { invalid = -1, @@ -149,6 +150,7 @@ namespace xo { x.print(os); return os; } +#endif class exprstatestack; diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 0fbe7946..6813ba8e 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -76,6 +76,7 @@ namespace xo { return "???"; } +#ifdef OBSOLETE const char * expractiontype_descr(expractiontype x) { switch(x) { @@ -101,6 +102,7 @@ namespace xo { os << xtag("type", action_type_); os << ">"; } +#endif bool exprstate::admits_definition() const { From 7d6941fe3f3264a814022d3d4dcbd9df1c09f2bd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 13:41:01 +1000 Subject: [PATCH 1274/2524] xo-parser: tidy: delete excluded code --- include/xo/parser/parser.hpp | 119 ----------------------------------- 1 file changed, 119 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index ebf2f7d6..a750affc 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -12,63 +12,6 @@ namespace xo { namespace scm { -#ifdef OBSOLETE - // ----- exprir ----- - - enum class exprirtype { - invalid = -1, - - empty, - symbol, - typedescr, - - n_exprirtype - }; - - extern const char * - exprirtype_descr(exprirtype x); - - inline std::ostream & - operator<< (std::ostream & os, - exprirtype x) - { - os << exprirtype_descr(x); - return os; - } - - /** intermediate representation for some part of an expression - * - * Examples: - * 1. a variable name (but without type information) - **/ - class exprir { - public: - using Expression = xo::ast::Expression; - using TypeDescr = xo::reflect::TypeDescr; - - public: - exprir() = default; - explicit exprir(exprirtype xir_type) - : xir_type_{xir_type} {} - - exprirtype xir_type() const { return xir_type_; } - //const std::string & symbol_name() const { return symbol_name_; } - //TypeDescr td() const { return td_; } - - void print(std::ostream & os) const; - - private: - /** IR type code **/ - exprirtype xir_type_ = exprirtype::invalid; - }; - - inline std::ostream & - operator<< (std::ostream & os, const exprir & x) { - x.print(os); - return os; - } -#endif - enum class exprstatetype { invalid = -1, @@ -97,61 +40,6 @@ namespace xo { return os; } -#ifdef OBSOLETE - enum class expractiontype { - invalid = -1, - - keep, - //emit, - //pop, - - n_expractiontype - }; - - extern const char * - expractiontype_descr(expractiontype x); - - inline std::ostream & - operator<< (std::ostream & os, expractiontype x) { - os << expractiontype_descr(x); - return os; - } - - /** an action associated with parser response to an incoming lexical - **/ - class expraction { - public: - expraction() = default; - explicit expraction(expractiontype action_type) - : action_type_{action_type} - {} - - static expraction keep(); - - expractiontype action_type() const { return action_type_; } - - void print(std::ostream & os) const; - - private: - /** - * push1: push new exprstate built from push_exs1_ - * push2: push new exprstate built from push_exs1_, - * followed by push_exs2_ - * keep: keep current exprstate (which will have updated inplace) - * pop: drop exprstate, report exprir to parent - **/ - expractiontype action_type_ = expractiontype::invalid; - }; - - inline std::ostream & - operator<< (std::ostream & os, - const expraction & x) - { - x.print(os); - return os; - } -#endif - class exprstatestack; /** state associated with a partially-parsed expression. @@ -208,13 +96,6 @@ namespace xo { void on_typedescr(TypeDescr td, exprstatestack * p_stack, rp * p_emit_expr); -#ifdef OBSOLETE - /** update exprstate in response to IR (intermediate representation) - * from nested parsing task - **/ - void on_exprir(const exprir & ir, exprstatestack * p_stack, rp * p_emit_expr); -#endif - /** print human-readable representation on @p os **/ void print(std::ostream & os) const; From 4e9b41645f76539393a859fe28313ad6b3de029f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 15:34:47 +1000 Subject: [PATCH 1275/2524] xo-parser: scaffolding eliminates exprstate::def_lhs_symbol --- include/xo/parser/parser.hpp | 12 +++++++- src/parser/parser.cpp | 60 ++++++++++-------------------------- 2 files changed, 27 insertions(+), 45 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index a750affc..ccba5080 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -6,6 +6,7 @@ #pragma once #include "xo/expression/Expression.hpp" +#include "xo/expression/DefineExpr.hpp" #include "xo/tokenizer/token.hpp" #include #include @@ -47,13 +48,17 @@ namespace xo { class exprstate { public: using Expression = xo::ast::Expression; + using DefineExprAccess = xo::ast::DefineExprAccess; using exprtype = xo::ast::exprtype; using token_type = token; using TypeDescr = xo::reflect::TypeDescr; public: exprstate() = default; - exprstate(exprstatetype exs_type) : exs_type_{exs_type} {} + exprstate(exprstatetype exs_type, + rp def_expr = nullptr) + : exs_type_{exs_type}, + def_expr_{std::move(def_expr)} {} static exprstate expect_toplevel_expression_sequence() { return exprstate(exprstatetype::expect_toplevel_expression_sequence); @@ -132,10 +137,15 @@ namespace xo { **/ exprstatetype exs_type_; + /** scaffold a define-expression here **/ + rp def_expr_; + +#ifdef OBSOLETE /** e.g. foo in * def foo : f64 = 1 **/ std::string def_lhs_symbol_; +#endif /** e.g. f64 in * def foo : f64 = 1 **/ diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 6813ba8e..e78728d9 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -258,7 +258,9 @@ namespace xo { xtag("state", *this))); } - p_stack->push_exprstate(exprstatetype::def_0); + p_stack->push_exprstate(exprstate(exprstatetype::def_0, + DefineExprAccess::make_empty())); + /* todo: replace: * expect_symbol_or_function_signature() */ @@ -367,6 +369,7 @@ namespace xo { case exprstatetype::def_2: this->exs_type_ = exprstatetype::def_3; + this->def_lhs_td_ = td; return; @@ -585,11 +588,18 @@ namespace xo { if (def_lhs_td_) rhs_value = ConvertExpr::make(def_lhs_td_, rhs_value); + rp def_expr = this->def_expr_; + + def_expr->assign_rhs(rhs_value); + +#ifdef OBSOLETE rp def = DefineExpr::make(this->def_lhs_symbol_, rhs_value); +#endif - p_stack->pop_exprstate(); - p_stack->top_exprstate().on_expr(def, + p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + + p_stack->top_exprstate().on_expr(def_expr, p_stack, p_emit_expr); return; @@ -629,7 +639,8 @@ namespace xo { return; case exprstatetype::def_0: this->exs_type_ = exprstatetype::def_1; - this->def_lhs_symbol_ = symbol_name; + this->def_expr_->assign_lhs_name(symbol_name); + //this->def_lhs_symbol_ = symbol_name; return; case exprstatetype::def_1: @@ -656,50 +667,11 @@ namespace xo { } } -#ifdef OBSOLETE - void - exprstate::on_exprir(const exprir & ir, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) - { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - log && log(xtag("ir", ir)); - log && log(xtag("state", *this)); - - switch(this->exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - /* NOT IMPLEMENTED */ - assert(false); - return; - - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_type: - case exprstatetype::expect_symbol: - /* unreachable - * (this exprstate issues pop instruction from exprstate::on_input() - */ - assert(false); - return; - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - assert(false); - return; - } - } -#endif - void exprstate::print(std::ostream & os) const { os << "short_name()); os << ">"; From c4c140af36069e981cac28af0279e4ad4a3d6aa2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 16:39:01 +1000 Subject: [PATCH 1276/2524] xo-parser: simplify: ConvertExprAccess replaces exprstate.def_lhs_td --- include/xo/parser/parser.hpp | 9 ++++----- src/parser/parser.cpp | 27 ++++++++++++--------------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index ccba5080..75dd22dd 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -7,6 +7,7 @@ #include "xo/expression/Expression.hpp" #include "xo/expression/DefineExpr.hpp" +#include "xo/expression/ConvertExpr.hpp" #include "xo/tokenizer/token.hpp" #include #include @@ -49,6 +50,7 @@ namespace xo { public: using Expression = xo::ast::Expression; using DefineExprAccess = xo::ast::DefineExprAccess; + using ConvertExprAccess = xo::ast::ConvertExprAccess; using exprtype = xo::ast::exprtype; using token_type = token; using TypeDescr = xo::reflect::TypeDescr; @@ -139,17 +141,14 @@ namespace xo { /** scaffold a define-expression here **/ rp def_expr_; + rp cvt_expr_; #ifdef OBSOLETE - /** e.g. foo in - * def foo : f64 = 1 - **/ - std::string def_lhs_symbol_; -#endif /** e.g. f64 in * def foo : f64 = 1 **/ TypeDescr def_lhs_td_ = nullptr; +#endif }; /*exprstate*/ inline std::ostream & diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index e78728d9..eef5f821 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -369,10 +369,13 @@ namespace xo { case exprstatetype::def_2: this->exs_type_ = exprstatetype::def_3; - - this->def_lhs_td_ = td; + this->cvt_expr_ = ConvertExprAccess::make(td /*dest_type*/, + nullptr /*source_expr*/); + this->def_expr_->assign_rhs(this->cvt_expr_); + //this->def_lhs_td_ = td; return; + case exprstatetype::def_3: case exprstatetype::def_4: /* NOT IMPLEMENTED */ @@ -585,17 +588,12 @@ namespace xo { */ rp rhs_value = expr.get(); - if (def_lhs_td_) - rhs_value = ConvertExpr::make(def_lhs_td_, rhs_value); + if (this->cvt_expr_) + this->cvt_expr_->assign_arg(rhs_value); + else + this->def_expr_->assign_rhs(rhs_value);; - rp def_expr = this->def_expr_; - - def_expr->assign_rhs(rhs_value); - -#ifdef OBSOLETE - rp def = DefineExpr::make(this->def_lhs_symbol_, - rhs_value); -#endif + rp def_expr = this->def_expr_; p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ @@ -671,9 +669,8 @@ namespace xo { exprstate::print(std::ostream & os) const { os << "short_name()); + << xtag("def_expr", def_expr_) + << xtag("cvt_expr", cvt_expr_); os << ">"; } From 4df9192586cb1b24678a0b3cb31f3ee4b7d84484 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 16:56:08 +1000 Subject: [PATCH 1277/2524] xo-parser: feature: def may omit explicit type --- include/xo/parser/parser.hpp | 10 +++------- src/parser/parser.cpp | 24 +++++++++++++++++++++++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index 75dd22dd..e4e8943a 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -141,14 +141,10 @@ namespace xo { /** scaffold a define-expression here **/ rp def_expr_; - rp cvt_expr_; - -#ifdef OBSOLETE - /** e.g. f64 in - * def foo : f64 = 1 + /** scafford a convert-expression here. + * May be nested within a def_expr **/ - TypeDescr def_lhs_td_ = nullptr; -#endif + rp cvt_expr_; }; /*exprstate*/ inline std::ostream & diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index eef5f821..79c9b73f 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -194,8 +194,27 @@ namespace xo { exprstate::admits_singleassign() const { switch(exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: + + /* + * def foo = 1 + * def foo : f64 = 1 + * ^ ^ ^ ^ ^ ^ ^ + * | | | | | | (done) + * | | | | | def_4:expect_rhs_expression + * | | | | def_3 + * | | | def_2:expect_type + * | | def_1 + * | def_0:expect_symbol + * expect_toplevel_expression_sequence + * + * note that we skip from def_1 -> def_4 if '=' instead of ':' + */ case exprstatetype::def_0: + return false; + case exprstatetype::def_1: + return true; + case exprstatetype::def_2: return false; @@ -203,6 +222,7 @@ namespace xo { return true; case exprstatetype::def_4: + case exprstatetype::expect_rhs_expression: /* rhs-expressions (or expressions for that matter) * may not begin with singleassign '=' @@ -437,7 +457,9 @@ namespace xo { xtag("state", *this))); } - if (this->exs_type_ == exprstatetype::def_3) { + if ((this->exs_type_ == exprstatetype::def_1) + || (this->exs_type_ == exprstatetype::def_3)) + { this->exs_type_ = exprstatetype::def_4; p_stack->push_exprstate(exprstatetype::expect_rhs_expression); From 32057efb5a79bcb4581cc4ea2840d082083c8474 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 1 Aug 2024 16:56:34 +1000 Subject: [PATCH 1278/2524] xo-parser: utest: unit test for def with/without explicit type --- utest/parser.test.cpp | 333 ++++++++++++++++++++++-------------------- 1 file changed, 178 insertions(+), 155 deletions(-) diff --git a/utest/parser.test.cpp b/utest/parser.test.cpp index e656d9de..8397f903 100644 --- a/utest/parser.test.cpp +++ b/utest/parser.test.cpp @@ -17,180 +17,203 @@ namespace xo { namespace ut { TEST_CASE("parser", "[parser]") { - parser_type parser; + for (std::size_t i_tc = 0; i_tc < 2; ++i_tc) { + parser_type parser; - parser.begin_translation_unit(); + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag), xtag("i_tc", i_tc)); - REQUIRE(parser.stack_size() == 1); - REQUIRE(parser.i_exstype(0) - == exprstatetype::expect_toplevel_expression_sequence); + parser.begin_translation_unit(); - /* input: - * def - */ - { - auto r1 = parser.include_token(token_type::def()); - REQUIRE(r1.get() == nullptr); + REQUIRE(parser.stack_size() == 1); + REQUIRE(parser.i_exstype(0) + == exprstatetype::expect_toplevel_expression_sequence); - /* stack should be: + /* input: + * def + */ + { + auto r1 = parser.include_token(token_type::def()); + REQUIRE(r1.get() == nullptr); + + /* stack should be: + * + * expect_toplevel_expression_sequence + * def_0 + * expect_symbol + */ + CHECK(parser.stack_size() == 3); + if (parser.stack_size() > 0) + CHECK(parser.i_exstype(0) == exprstatetype::expect_symbol); + if (parser.stack_size() > 1) + CHECK(parser.i_exstype(1) == exprstatetype::def_0); + if (parser.stack_size() > 2) + CHECK(parser.i_exstype(2) + == exprstatetype::expect_toplevel_expression_sequence); + } + + /* input: + * def foo + * ^ ^ + * 0 1 + */ + { + auto r2 = parser.include_token(token_type::symbol_token("foo")); + + cerr << "parser state after [def foo]" << endl; + cerr << parser << endl; + + REQUIRE(r2.get() == nullptr); + + /* stack should be: + * + * expect_toplevel_expression_sequence + * def_1 + */ + CHECK(parser.stack_size() == 2); + if (parser.stack_size() > 0) + CHECK(parser.i_exstype(0) == exprstatetype::def_1); + if (parser.stack_size() > 1) + CHECK(parser.i_exstype(1) + == exprstatetype::expect_toplevel_expression_sequence); + + } + + if (i_tc == 0) { + ; + } else if (i_tc == 1) { + /* input: + * def foo : + * ^ ^ + * 0 1 + */ + { + auto r3 = parser.include_token(token_type::colon()); + + cerr << "parser state after [def foo :]" << endl; + cerr << parser << endl; + + REQUIRE(r3.get() == nullptr); + + /* stack should be: + * + * expect_toplevel_expression_sequence + * def_2 + * expect_symbol + */ + CHECK(parser.stack_size() == 3); + if (parser.stack_size() > 0) + CHECK(parser.i_exstype(0) == exprstatetype::expect_type); + if (parser.stack_size() > 1) + CHECK(parser.i_exstype(1) == exprstatetype::def_2); + if (parser.stack_size() > 2) + CHECK(parser.i_exstype(2) + == exprstatetype::expect_toplevel_expression_sequence); + } + + /* input: + * def foo : f64 + * ^ ^ + * 0 1 + */ + { + auto r4 = parser.include_token(token_type::symbol_token("f64")); + + cerr << "parser state after [def foo : f64]" << endl; + cerr << parser << endl; + + REQUIRE(r4.get() == nullptr); + + CHECK(parser.stack_size() == 2); + + /* stack should be: + * + * expect_toplevel_expression_sequence + * def_3 + */ + CHECK(parser.stack_size() == 2); + if (parser.stack_size() > 0) + CHECK(parser.i_exstype(0) == exprstatetype::def_3); + if (parser.stack_size() > 1) + CHECK(parser.i_exstype(1) + == exprstatetype::expect_toplevel_expression_sequence); + + /* expecting either: + * = rhs-expression + * new-expression + */ + } + } + + /* input: * - * expect_toplevel_expression_sequence - * def_0 - * expect_symbol - */ - CHECK(parser.stack_size() == 3); - if (parser.stack_size() > 0) - CHECK(parser.i_exstype(0) == exprstatetype::expect_symbol); - if (parser.stack_size() > 1) - CHECK(parser.i_exstype(1) == exprstatetype::def_0); - if (parser.stack_size() > 2) - CHECK(parser.i_exstype(2) - == exprstatetype::expect_toplevel_expression_sequence); - } - - /* input: - * def foo - * ^ ^ - * 0 1 - */ - { - auto r2 = parser.include_token(token_type::symbol_token("foo")); - - cerr << "parser state after [def foo]" << endl; - cerr << parser << endl; - - REQUIRE(r2.get() == nullptr); - - /* stack should be: + * i_tc==0: + * def foo = + * ^ ^ + * 0 1 * - * expect_toplevel_expression_sequence - * def_1 + * i_tc==1: + * def foo : f64 = + * ^ ^ + * 0 1 */ - CHECK(parser.stack_size() == 2); - if (parser.stack_size() > 0) - CHECK(parser.i_exstype(0) == exprstatetype::def_1); - if (parser.stack_size() > 1) - CHECK(parser.i_exstype(1) - == exprstatetype::expect_toplevel_expression_sequence); + { + auto r5 = parser.include_token(token_type::singleassign()); - } + cerr << "parser state after [def foo : f64 =]" << endl; + cerr << parser << endl; - /* input: - * def foo : - * ^ ^ - * 0 1 - */ - { - auto r3 = parser.include_token(token_type::colon()); + REQUIRE(r5.get() == nullptr); - cerr << "parser state after [def foo :]" << endl; - cerr << parser << endl; + CHECK(parser.stack_size() == 3); - REQUIRE(r3.get() == nullptr); + /* stack should be + * + * expect_toplevel_expression_sequence + * def_4 + * expect_expression + */ + CHECK(parser.stack_size() == 3); + if (parser.stack_size() > 0) + CHECK(parser.i_exstype(0) == exprstatetype::expect_rhs_expression); + if (parser.stack_size() > 1) + CHECK(parser.i_exstype(1) == exprstatetype::def_4); + if (parser.stack_size() > 2) + CHECK(parser.i_exstype(2) + == exprstatetype::expect_toplevel_expression_sequence); + } - /* stack should be: + /* input: * - * expect_toplevel_expression_sequence - * def_2 - * expect_symbol - */ - CHECK(parser.stack_size() == 3); - if (parser.stack_size() > 0) - CHECK(parser.i_exstype(0) == exprstatetype::expect_type); - if (parser.stack_size() > 1) - CHECK(parser.i_exstype(1) == exprstatetype::def_2); - if (parser.stack_size() > 2) - CHECK(parser.i_exstype(2) - == exprstatetype::expect_toplevel_expression_sequence); - } - - /* input: - * def foo : f64 - * ^ ^ - * 0 1 - */ - { - auto r4 = parser.include_token(token_type::symbol_token("f64")); - - cerr << "parser state after [def foo : f64]" << endl; - cerr << parser << endl; - - REQUIRE(r4.get() == nullptr); - - CHECK(parser.stack_size() == 2); - - /* stack should be: + * i_tc==0: + * def foo = 3.14159265 + * ^ ^ + * 0 1 * - * expect_toplevel_expression_sequence - * def_3 + * i_tc==1: + * def foo : f64 = 3.14159265 + * ^ ^ + * 0 1 */ - CHECK(parser.stack_size() == 2); - if (parser.stack_size() > 0) - CHECK(parser.i_exstype(0) == exprstatetype::def_3); - if (parser.stack_size() > 1) - CHECK(parser.i_exstype(1) - == exprstatetype::expect_toplevel_expression_sequence); + { + auto r6 = parser.include_token(token_type::f64_token("3.14159265")); - /* expecting either: - * = rhs-expression - * new-expression - */ - } + cerr << "parser state after [def foo : f64 = 3.14159265]" << endl; + cerr << parser << endl; - /* input: - * def foo : f64 = - * ^ ^ - * 0 1 - */ - { - auto r5 = parser.include_token(token_type::singleassign()); + REQUIRE(r6.get() != nullptr); - cerr << "parser state after [def foo : f64 =]" << endl; - cerr << parser << endl; + CHECK(parser.stack_size() == 1); - REQUIRE(r5.get() == nullptr); - - CHECK(parser.stack_size() == 3); - - /* stack should be - * - * expect_toplevel_expression_sequence - * def_4 - * expect_expression - */ - CHECK(parser.stack_size() == 3); - if (parser.stack_size() > 0) - CHECK(parser.i_exstype(0) == exprstatetype::expect_rhs_expression); - if (parser.stack_size() > 1) - CHECK(parser.i_exstype(1) == exprstatetype::def_4); - if (parser.stack_size() > 2) - CHECK(parser.i_exstype(2) - == exprstatetype::expect_toplevel_expression_sequence); - } - - /* input: - * def foo : f64 = 3.14159265 - * ^ ^ - * 0 1 - */ - { - auto r6 = parser.include_token(token_type::f64_token("3.14159265")); - - cerr << "parser state after [def foo : f64 = 3.14159265]" << endl; - cerr << parser << endl; - - REQUIRE(r6.get() != nullptr); - - CHECK(parser.stack_size() == 1); - - /* stack should be - * - * expect_toplevel_expression_sequence - */ - CHECK(parser.stack_size() == 1); - if (parser.stack_size() > 0) - CHECK(parser.i_exstype(0) - == exprstatetype::expect_toplevel_expression_sequence); + /* stack should be + * + * expect_toplevel_expression_sequence + */ + CHECK(parser.stack_size() == 1); + if (parser.stack_size() > 0) + CHECK(parser.i_exstype(0) + == exprstatetype::expect_toplevel_expression_sequence); + } } } /*TEST_CASE(parser)*/ } /*namespace ut*/ From 0c45d2b883ab27b960c90c48e6701f1f136c1df1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:28:14 -0400 Subject: [PATCH 1279/2524] xo-refcnt: ns: xo::ref::rp -> xo::rp --- include/xo/refcnt/Refcounted.hpp | 11 ++++++++--- utest/intrusive_ptr.test.cpp | 1 - 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/include/xo/refcnt/Refcounted.hpp b/include/xo/refcnt/Refcounted.hpp index 33c5b377..a9d4386b 100644 --- a/include/xo/refcnt/Refcounted.hpp +++ b/include/xo/refcnt/Refcounted.hpp @@ -10,6 +10,14 @@ #include namespace xo { + namespace ref { + template + class intrusive_ptr; + } + + template + using rp = ref::intrusive_ptr; + namespace ref { class Refcount; @@ -153,9 +161,6 @@ namespace xo { inline bool operator==(intrusive_ptr const & x, intrusive_ptr const & y) { return intrusive_ptr::compare(x, y) == 0; } - template - using rp = intrusive_ptr; - class Refcount { public: Refcount() : reference_counter_(0) {} diff --git a/utest/intrusive_ptr.test.cpp b/utest/intrusive_ptr.test.cpp index 112268a9..78e3181a 100644 --- a/utest/intrusive_ptr.test.cpp +++ b/utest/intrusive_ptr.test.cpp @@ -9,7 +9,6 @@ namespace xo { using xo::ref::Refcount; using xo::ref::Borrow; - using xo::ref::rp; using xo::ref::brw; using xo::ref::intrusive_ptr_refcount; using xo::ref::intrusive_ptr_add_ref; From 57eee82fa55d618694efd58b0e046575f314b25c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:31:04 -0400 Subject: [PATCH 1280/2524] .gitignore: + .projectile (emacs ws config) --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 9648517d..8e2ef56b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +# emacs workspace config +.projectile # symlink to ${mybuilddirectory}/compile_commands.json for LSP compile_commands.json # LSP keeps state here From 102fbf4a160161c15475e83ad9c0bacac7d38ea6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:34:14 -0400 Subject: [PATCH 1281/2524] xo-ratio: + xo_reflectutil feature + utest --- CMakeLists.txt | 1 + include/xo/ratio/ratio_reflect.hpp | 25 +++++++++++++++++++++++ utest/CMakeLists.txt | 2 ++ utest/ratio_reflect.test.cpp | 32 ++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 include/xo/ratio/ratio_reflect.hpp create mode 100644 utest/ratio_reflect.test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a365f30..4763965c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ add_subdirectory(docs) # ---------------------------------------------------------------- # dependencies +xo_headeronly_dependency(${SELF_LIB} xo_reflectutil) xo_headeronly_dependency(${SELF_LIB} xo_flatstring) #xo_headeronly_dependency(${SELF_LIB} randomgen) # etc.. diff --git a/include/xo/ratio/ratio_reflect.hpp b/include/xo/ratio/ratio_reflect.hpp new file mode 100644 index 00000000..2d7e8da8 --- /dev/null +++ b/include/xo/ratio/ratio_reflect.hpp @@ -0,0 +1,25 @@ +/** @file ratio_reflect.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/reflectutil/reflect_struct_info.hpp" +#include "ratio.hpp" +//#include + +namespace xo { + namespace reflect { + template + REFLECT_BASE_STRUCT_INFO_TBODY(xo::ratio::ratio, 2); + + template + REFLECT_STRUCT_MEMBER_INFO_TBODY(xo::ratio::ratio, 0, num); + + template + REFLECT_STRUCT_MEMBER_INFO_TBODY(xo::ratio::ratio, 1, den); + } +} + +/** end ratio_reflect.hpp **/ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 052961ec..2aac113a 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -3,6 +3,7 @@ set(SELF_EXE utest.ratio) set(SELF_SRCS ratio_utest_main.cpp + ratio_reflect.test.cpp ratio.test.cpp) if (ENABLE_TESTING) @@ -14,6 +15,7 @@ if (ENABLE_TESTING) # dependencies.. xo_self_headeronly_dependency(${SELF_EXE} xo_ratio) + xo_dependency(${SELF_EXE} reflect) xo_dependency(${SELF_EXE} randomgen) xo_dependency(${SELF_EXE} indentlog) xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) diff --git a/utest/ratio_reflect.test.cpp b/utest/ratio_reflect.test.cpp new file mode 100644 index 00000000..2e6b78ee --- /dev/null +++ b/utest/ratio_reflect.test.cpp @@ -0,0 +1,32 @@ +/* @file ratio_reflect.test.cpp */ + +#include "xo/ratio/ratio_reflect.hpp" +#include "xo/reflect/reflect_struct.hpp" +#include + +namespace xo { + namespace ut { + TEST_CASE("ratio_reflect", "[ratio][reflect]") { + using xo::reflect::reflect_struct; + using xo::reflect::TypeDescrBase; + using xo::reflect::TypeDescr; + using xo::ratio::ratio; + + /* verify ratio reflection */ + + TypeDescr td = reflect_struct>(); + + REQUIRE(td->is_struct()); + REQUIRE(td->metatype() == xo::reflect::Metatype::mt_struct); + REQUIRE(td->n_child(nullptr) == 2); + REQUIRE(td->struct_member(0).member_name() == "num"); + REQUIRE(td->struct_member(0).get_member_td()->short_name() == "long int"); + REQUIRE(td->struct_member(1).member_name() == "den"); + REQUIRE(td->struct_member(1).get_member_td()->short_name() == "long int"); + + TypeDescrBase::print_reflected_types(std::cerr); + } + } /*namespace ut*/ +} /*namespace xo*/ + +/* end ratio_reflect.test.cpp */ From fc2b88da4b0336bc47d237554ba5da293ee954af Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:37:35 -0400 Subject: [PATCH 1282/2524] xo-reflect: feat: capture dtor --- include/xo/reflect/EstablishTypeDescr.hpp | 3 +- include/xo/reflect/Object.hpp | 2 - include/xo/reflect/Reflect.hpp | 11 +++-- include/xo/reflect/StructReflector.hpp | 5 +- include/xo/reflect/TypeDescr.hpp | 58 ++++++++++++++++++++--- src/reflect/TypeDescr.cpp | 9 +++- utest/CMakeLists.txt | 4 -- 7 files changed, 71 insertions(+), 21 deletions(-) diff --git a/include/xo/reflect/EstablishTypeDescr.hpp b/include/xo/reflect/EstablishTypeDescr.hpp index 423345dc..0f18d84a 100644 --- a/include/xo/reflect/EstablishTypeDescr.hpp +++ b/include/xo/reflect/EstablishTypeDescr.hpp @@ -41,7 +41,8 @@ namespace xo { static TypeDescrW establish() { TypeDescrW td = TypeDescrBase::require(&typeid(T), std::string(type_name()), - nullptr); + nullptr /*tdextra*/, + nullptr /*invoker*/); #ifdef NOT_USING std::function to_self_tp; diff --git a/include/xo/reflect/Object.hpp b/include/xo/reflect/Object.hpp index fc7f1607..91a5cedd 100644 --- a/include/xo/reflect/Object.hpp +++ b/include/xo/reflect/Object.hpp @@ -21,8 +21,6 @@ namespace xo { **/ class Object : public reflect::SelfTagging { public: - using TypeId = xo::reflect::TypeId; - Object(TypeId type_id) : type_id_{type_id} {} private: diff --git a/include/xo/reflect/Reflect.hpp b/include/xo/reflect/Reflect.hpp index 23179c13..d4cfd34c 100644 --- a/include/xo/reflect/Reflect.hpp +++ b/include/xo/reflect/Reflect.hpp @@ -36,7 +36,7 @@ namespace xo { // ----- xo::ref::rp ----- template - class EstablishTdx> { + class EstablishTdx> { public: /* definition provide after decl for Reflect {} below */ static std::unique_ptr make(); @@ -182,9 +182,12 @@ namespace xo { } else { /* control comes here the first time require() runs */ + static detail::InvokerAux s_final_invoker; + auto final_tdx = EstablishTdx::make(); - retval_td->assign_tdextra(std::move(final_tdx)); + retval_td->assign_tdextra(&s_final_invoker, + std::move(final_tdx)); /* also need to require for each child */ } @@ -274,7 +277,7 @@ namespace xo { */ template std::unique_ptr - EstablishTdx>::make() { + EstablishTdx>::make() { /* need to ensure Object is property reflected. * * In practice must be a class type, since has to store refcount @@ -282,7 +285,7 @@ namespace xo { */ Reflect::require(); - return RefPointerTdx>::make(); + return RefPointerTdx>::make(); } /*make*/ // ----- std::array ----- diff --git a/include/xo/reflect/StructReflector.hpp b/include/xo/reflect/StructReflector.hpp index 220990a5..b443950f 100644 --- a/include/xo/reflect/StructReflector.hpp +++ b/include/xo/reflect/StructReflector.hpp @@ -80,11 +80,14 @@ namespace xo { return SelfTagger::self_tp(object); }); + static detail::InvokerAux s_final_invoker; + auto tdx = StructTdx::make(std::move(this->member_v_), have_to_self_tp, to_self_tp_fn); - this->td_->assign_tdextra(std::move(tdx)); + this->td_->assign_tdextra(&s_final_invoker, + std::move(tdx)); } } /*complete*/ diff --git a/include/xo/reflect/TypeDescr.hpp b/include/xo/reflect/TypeDescr.hpp index 95d9364c..53247ad0 100644 --- a/include/xo/reflect/TypeDescr.hpp +++ b/include/xo/reflect/TypeDescr.hpp @@ -88,6 +88,33 @@ namespace xo { /* native type_info object for encapsulated type */ std::type_info const * tinfo_ = nullptr; }; /*TypeInfoRef*/ + + namespace detail { + struct Invoker { + virtual void dtor(void * addr) const = 0; + }; + + /** Auxiliary template for capturing destructor for type T, + * if it has one. + * + * Example + * T * p = new T(...); + * DestructorAux::dtor(p); + **/ + template + struct InvokerAux : public Invoker { + virtual void dtor(void * addr) const override { + T * obj = static_cast(addr); + + obj->~T(); + } + }; + + template<> + struct InvokerAux : public Invoker { + virtual void dtor(void *) const override {} + }; + } /*namespace detail*/ } /*namespace reflect*/ } /*namespace xo*/ @@ -191,6 +218,7 @@ namespace xo { */ static TypeDescrW require(const std::type_info * tinfo, const std::string & canonical_name, + detail::Invoker * invoker, std::unique_ptr tdextra); /** Create type-description for function from input ingredients. **/ @@ -364,16 +392,21 @@ namespace xo { /* call this once to attach extended type information to a type-description * (e.g. description of struct members for a record type) */ - void assign_tdextra(std::unique_ptr tdx) { + void assign_tdextra(detail::Invoker * invoker, + std::unique_ptr tdx) { this->complete_flag_ = true; + this->invoker_ = invoker; this->tdextra_ = std::move(tdx); } + // ----- actions ----- + private: TypeDescrBase(TypeId id, const std::type_info * tinfo, const std::string & canonical_name, - std::unique_ptr tdextra); + std::unique_ptr tdextra, + detail::Invoker * invoker); void assign_native_tinfo(const std::type_info * tinfo) { assert(!native_typeinfo_); @@ -429,8 +462,9 @@ namespace xo { static std::unordered_map s_function_type_map; private: - /* unique id# for this type */ + /** unique id# for this type **/ TypeId id_; + /** typeinfo for type T, if available. nullptr otherwise. * * 1. Always available for type-descriptions constructed via Reflect::require. @@ -438,6 +472,7 @@ namespace xo { * see Lambda.cpp in xo-expression. **/ std::type_info const * native_typeinfo_ = nullptr; + /** canonical name for this type (see demangle.hpp for type_name()) * e.g. * xo::option::Px2 @@ -448,19 +483,28 @@ namespace xo { * so need to use std::string here **/ std::string canonical_name_; + /** substring .canonical_name, just after last ':' * e.g. * Px2 **/ std::string_view short_name_; - /* set to true once final value for .tdextra is established + + /** 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: + + /** capture basic instance-management operations for this type. + * Given an instance T x: + * - invoker_->dtor(&x) invokes T::~T() + **/ + detail::Invoker * invoker_; + + /** additional type information that either: * (a) isn't universal across all types, * e.g. dereferencing instance of a pointer type * (b) can't be captured with template-fu, @@ -469,7 +513,7 @@ namespace xo { * generally .tdextra will be populated some time after TypeDescrBase's ctor exits. * This is necessary because of (b) above, also because of possibility of recursive * types. - */ + **/ std::unique_ptr tdextra_; }; /*TypeDescrBase*/ diff --git a/src/reflect/TypeDescr.cpp b/src/reflect/TypeDescr.cpp index 4d9e3d50..4d048015 100644 --- a/src/reflect/TypeDescr.cpp +++ b/src/reflect/TypeDescr.cpp @@ -54,6 +54,7 @@ namespace xo { TypeDescrW TypeDescrBase::require(const std::type_info * native_tinfo, const std::string & canonical_name, + detail::Invoker * invoker, std::unique_ptr tdextra) { if (native_tinfo) { @@ -152,7 +153,8 @@ namespace xo { auto new_td = new TypeDescrBase(new_td_id, native_tinfo, canonical_name, - std::move(tdextra)); + std::move(tdextra), + invoker); new_slot.reset(new_td); @@ -178,6 +180,7 @@ namespace xo { return require(nullptr /*native_tinfo - n/avail on this path*/, fn_info.make_canonical_name(), + nullptr /*invoker*/, std::move(fn_tdextra)); } /*require_by_fn_info*/ @@ -240,11 +243,13 @@ namespace xo { TypeDescrBase::TypeDescrBase(TypeId id, const std::type_info * native_tinfo, const std::string & canonical_name, - std::unique_ptr tdextra) + std::unique_ptr tdextra, + detail::Invoker * invoker) : id_{std::move(id)}, native_typeinfo_{native_tinfo}, canonical_name_{std::move(canonical_name)}, short_name_{unqualified_name(canonical_name_)}, + invoker_{invoker}, tdextra_{std::move(tdextra)} { } diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index edc997ea..535907a5 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -9,10 +9,6 @@ set(SELF_SOURCE_FILES FunctionTdx.test.cpp) xo_add_utest_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES}) - -# ---------------------------------------------------------------- -# deps: indentlog, .. - xo_self_dependency(${SELF_EXECUTABLE_NAME} reflect) xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2) From 8b631bbcc1467abbf9de5c48a140bc81181dc68d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:37:54 -0400 Subject: [PATCH 1283/2524] xo-reflect: tidy: formatting in utest --- utest/VectorTdx.test.cpp | 262 +++++++++++++++++++-------------------- 1 file changed, 131 insertions(+), 131 deletions(-) diff --git a/utest/VectorTdx.test.cpp b/utest/VectorTdx.test.cpp index 00f30a48..fe4e58a4 100644 --- a/utest/VectorTdx.test.cpp +++ b/utest/VectorTdx.test.cpp @@ -7,175 +7,175 @@ #include namespace xo { - using xo::reflect::Reflect; - using xo::reflect::TaggedPtr; - using xo::reflect::TypeDescr; - using xo::reflect::Metatype; + using xo::reflect::Reflect; + using xo::reflect::TaggedPtr; + using xo::reflect::TypeDescr; + using xo::reflect::Metatype; - namespace ut { - TEST_CASE("std-vector-reflect-empty", "[reflect]") { - std::vector v; + namespace ut { + TEST_CASE("std-vector-reflect-empty", "[reflect]") { + std::vector v; - TaggedPtr tp = Reflect::make_tp(&v); - //TypeDescr td = Reflect::require>(); + TaggedPtr tp = Reflect::make_tp(&v); + //TypeDescr td = Reflect::require>(); - REQUIRE(Reflect::is_reflected>() == true); + REQUIRE(Reflect::is_reflected>() == true); - REQUIRE(tp.td()->complete_flag()); - REQUIRE(tp.address() == &v); - REQUIRE(tp.is_vector()); - REQUIRE(tp.is_struct() == false); - REQUIRE(tp.td()->metatype() == Metatype::mt_vector); - REQUIRE(tp.recover_native>() == &v); - REQUIRE(tp.n_child() == 0); /*since empty vector*/ - // REQUIRE(tp.child_td(0) == ... - } /*TEST_CASE(std-vector-reflect-empty)*/ + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 0); /*since empty vector*/ + // REQUIRE(tp.child_td(0) == ... + } /*TEST_CASE(std-vector-reflect-empty)*/ - TEST_CASE("std-vector-reflect-one", "[reflect]") { - std::vector v = { 1.123 }; + TEST_CASE("std-vector-reflect-one", "[reflect]") { + std::vector v = { 1.123 }; - TaggedPtr tp = Reflect::make_tp(&v); + TaggedPtr tp = Reflect::make_tp(&v); - REQUIRE(Reflect::is_reflected>() == true); + REQUIRE(Reflect::is_reflected>() == true); - REQUIRE(tp.td()->complete_flag()); - REQUIRE(tp.address() == &v); - REQUIRE(tp.is_vector()); - REQUIRE(tp.is_struct() == false); - REQUIRE(tp.td()->metatype() == Metatype::mt_vector); - REQUIRE(tp.recover_native>() == &v); - REQUIRE(tp.n_child() == 1); + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 1); - TaggedPtr tp0 = tp.get_child(0); + TaggedPtr tp0 = tp.get_child(0); - REQUIRE(tp0.td()->complete_flag()); - REQUIRE(tp0.address() == &(v[0])); - REQUIRE(!tp0.is_vector()); - REQUIRE(!tp0.is_struct()); - REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); - REQUIRE(tp0.recover_native() == &(v[0])); - REQUIRE(tp0.n_child() == 0); - } /*TEST_CASE(std-vector-reflect-one)*/ + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); + } /*TEST_CASE(std-vector-reflect-one)*/ - TEST_CASE("std-vector-reflect-two", "[reflect]") { - std::vector v = { 1.123, 2.234 }; + TEST_CASE("std-vector-reflect-two", "[reflect]") { + std::vector v = { 1.123, 2.234 }; - TaggedPtr tp = Reflect::make_tp(&v); + TaggedPtr tp = Reflect::make_tp(&v); - REQUIRE(Reflect::is_reflected>() == true); + REQUIRE(Reflect::is_reflected>() == true); - REQUIRE(tp.td()->complete_flag()); - REQUIRE(tp.address() == &v); - REQUIRE(tp.is_vector()); - REQUIRE(tp.is_struct() == false); - REQUIRE(tp.td()->metatype() == Metatype::mt_vector); - REQUIRE(tp.recover_native>() == &v); - REQUIRE(tp.n_child() == 2); + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 2); - TaggedPtr tp0 = tp.get_child(0); + TaggedPtr tp0 = tp.get_child(0); - REQUIRE(tp0.td()->complete_flag()); - REQUIRE(tp0.address() == &(v[0])); - REQUIRE(!tp0.is_vector()); - REQUIRE(!tp0.is_struct()); - REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); - REQUIRE(tp0.recover_native() == &(v[0])); - REQUIRE(tp0.n_child() == 0); + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); - TaggedPtr tp1 = tp.get_child(1); + TaggedPtr tp1 = tp.get_child(1); - REQUIRE(tp1.td()->complete_flag()); - REQUIRE(tp1.address() == &(v[1])); - REQUIRE(!tp1.is_vector()); - REQUIRE(!tp1.is_struct()); - REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); - REQUIRE(tp1.recover_native() == &(v[1])); - REQUIRE(tp1.n_child() == 0); - } /*TEST(std-vector-reflect-two)*/ + REQUIRE(tp1.td()->complete_flag()); + REQUIRE(tp1.address() == &(v[1])); + REQUIRE(!tp1.is_vector()); + REQUIRE(!tp1.is_struct()); + REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp1.recover_native() == &(v[1])); + REQUIRE(tp1.n_child() == 0); + } /*TEST(std-vector-reflect-two)*/ - // ----- std::array ----- + // ----- std::array ----- - TEST_CASE("std-array-reflect-empty", "[reflect]") { - std::array v; + TEST_CASE("std-array-reflect-empty", "[reflect]") { + std::array v; - TaggedPtr tp = Reflect::make_tp(&v); - //TypeDescr td = Reflect::require>(); + TaggedPtr tp = Reflect::make_tp(&v); + //TypeDescr td = Reflect::require>(); - REQUIRE(Reflect::is_reflected>() == true); + REQUIRE(Reflect::is_reflected>() == true); - REQUIRE(tp.td()->complete_flag()); - REQUIRE(tp.address() == &v); - REQUIRE(tp.is_vector()); - REQUIRE(tp.is_struct() == false); - REQUIRE(tp.td()->metatype() == Metatype::mt_vector); - REQUIRE(tp.recover_native>() == &v); - REQUIRE(tp.n_child() == 0); /*since empty vector*/ - // REQUIRE(tp.child_td(0) == ... - } /*TEST_CASE(std-array-reflect-empty)*/ + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 0); /*since empty vector*/ + // REQUIRE(tp.child_td(0) == ... + } /*TEST_CASE(std-array-reflect-empty)*/ - TEST_CASE("std-array-reflect-one", "[reflect]") { - std::array v = { 1.123 }; + TEST_CASE("std-array-reflect-one", "[reflect]") { + std::array v = { 1.123 }; - TaggedPtr tp = Reflect::make_tp(&v); + TaggedPtr tp = Reflect::make_tp(&v); - REQUIRE(Reflect::is_reflected>() == true); + REQUIRE(Reflect::is_reflected>() == true); - REQUIRE(tp.td()->complete_flag()); - REQUIRE(tp.address() == &v); - REQUIRE(tp.is_vector()); - REQUIRE(tp.is_struct() == false); - REQUIRE(tp.td()->metatype() == Metatype::mt_vector); - REQUIRE(tp.recover_native>() == &v); - REQUIRE(tp.n_child() == 1); + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 1); - TaggedPtr tp0 = tp.get_child(0); + TaggedPtr tp0 = tp.get_child(0); - REQUIRE(tp0.td()->complete_flag()); - REQUIRE(tp0.address() == &(v[0])); - REQUIRE(!tp0.is_vector()); - REQUIRE(!tp0.is_struct()); - REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); - REQUIRE(tp0.recover_native() == &(v[0])); - REQUIRE(tp0.n_child() == 0); - } /*TEST_CASE(std-array-reflect-one)*/ + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); + } /*TEST_CASE(std-array-reflect-one)*/ - TEST_CASE("std-array-reflect-two", "[reflect]") { - std::array v = { 1.123, 2.234 }; + TEST_CASE("std-array-reflect-two", "[reflect]") { + std::array v = { 1.123, 2.234 }; - TaggedPtr tp = Reflect::make_tp(&v); + TaggedPtr tp = Reflect::make_tp(&v); - REQUIRE(Reflect::is_reflected>() == true); + REQUIRE(Reflect::is_reflected>() == true); - REQUIRE(tp.td()->complete_flag()); - REQUIRE(tp.address() == &v); - REQUIRE(tp.is_vector()); - REQUIRE(tp.is_struct() == false); - REQUIRE(tp.td()->metatype() == Metatype::mt_vector); - REQUIRE(tp.recover_native>() == &v); - REQUIRE(tp.n_child() == 2); + REQUIRE(tp.td()->complete_flag()); + REQUIRE(tp.address() == &v); + REQUIRE(tp.is_vector()); + REQUIRE(tp.is_struct() == false); + REQUIRE(tp.td()->metatype() == Metatype::mt_vector); + REQUIRE(tp.recover_native>() == &v); + REQUIRE(tp.n_child() == 2); - TaggedPtr tp0 = tp.get_child(0); + TaggedPtr tp0 = tp.get_child(0); - REQUIRE(tp0.td()->complete_flag()); - REQUIRE(tp0.address() == &(v[0])); - REQUIRE(!tp0.is_vector()); - REQUIRE(!tp0.is_struct()); - REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); - REQUIRE(tp0.recover_native() == &(v[0])); - REQUIRE(tp0.n_child() == 0); + REQUIRE(tp0.td()->complete_flag()); + REQUIRE(tp0.address() == &(v[0])); + REQUIRE(!tp0.is_vector()); + REQUIRE(!tp0.is_struct()); + REQUIRE(tp0.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp0.recover_native() == &(v[0])); + REQUIRE(tp0.n_child() == 0); - TaggedPtr tp1 = tp.get_child(1); + TaggedPtr tp1 = tp.get_child(1); - REQUIRE(tp1.td()->complete_flag()); - REQUIRE(tp1.address() == &(v[1])); - REQUIRE(!tp1.is_vector()); - REQUIRE(!tp1.is_struct()); - REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); - REQUIRE(tp1.recover_native() == &(v[1])); - REQUIRE(tp1.n_child() == 0); - } /*TEST(std-array-reflect-two)*/ + REQUIRE(tp1.td()->complete_flag()); + REQUIRE(tp1.address() == &(v[1])); + REQUIRE(!tp1.is_vector()); + REQUIRE(!tp1.is_struct()); + REQUIRE(tp1.td()->metatype() == Metatype::mt_atomic); + REQUIRE(tp1.recover_native() == &(v[1])); + REQUIRE(tp1.n_child() == 0); + } /*TEST(std-array-reflect-two)*/ - } /*namespace ut*/ + } /*namespace ut*/ } /*namespace xo*/ /* end VectorTdx.test.cpp */ From 2d0336058e845acdb98a249a1812d1651d536b70 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:44:39 -0400 Subject: [PATCH 1284/2524] xo-tokenizer: move to scm ns + print() diagnostic --- include/xo/tokenizer/buffer.hpp | 4 ++-- include/xo/tokenizer/span.hpp | 4 ++-- include/xo/tokenizer/token.hpp | 26 ++++++++++++++++++++++++-- include/xo/tokenizer/tokenizer.hpp | 4 ++-- include/xo/tokenizer/tokentype.hpp | 8 +++++--- src/tokenizer/tokentype.cpp | 10 ++++++++-- utest/token.test.cpp | 4 ++-- utest/tokenizer.test.cpp | 8 ++++---- 8 files changed, 49 insertions(+), 19 deletions(-) diff --git a/include/xo/tokenizer/buffer.hpp b/include/xo/tokenizer/buffer.hpp index eb206eac..bc3621f2 100644 --- a/include/xo/tokenizer/buffer.hpp +++ b/include/xo/tokenizer/buffer.hpp @@ -9,7 +9,7 @@ #include namespace xo { - namespace tok { + namespace scm { /** * @class buffer buffer.hpp * @@ -318,7 +318,7 @@ namespace xo { swap(buffer & lhs, buffer & rhs) { lhs.swap(rhs); } - } /*namespace tok*/ + } /*namespace scm*/ } /*namespace xo*/ /* end buffer.hpp */ diff --git a/include/xo/tokenizer/span.hpp b/include/xo/tokenizer/span.hpp index 2f847f6a..36353a63 100644 --- a/include/xo/tokenizer/span.hpp +++ b/include/xo/tokenizer/span.hpp @@ -7,7 +7,7 @@ #include namespace xo { - namespace tok { + namespace scm { /** @class span compression/span.hpp * * @brief Represents a contiguous memory range, without ownership. @@ -137,5 +137,5 @@ namespace xo { x.print(os); return os; } - } /*namespace tok*/ + } /*namespace scm*/ } /*namespace xo*/ diff --git a/include/xo/tokenizer/token.hpp b/include/xo/tokenizer/token.hpp index 643a5143..3883e15a 100644 --- a/include/xo/tokenizer/token.hpp +++ b/include/xo/tokenizer/token.hpp @@ -8,11 +8,12 @@ #include "tokentype.hpp" #include "xo/indentlog/print/tag.hpp" #include +#include #include #include namespace xo { - namespace tok { + namespace scm { namespace detail { /* compute a * b^p, p >= 0 */ constexpr double @@ -101,6 +102,9 @@ namespace xo { **/ double f64_value() const; + /** print human-readable token representation on stream @p os **/ + void print(std::ostream & os) const; + private: /** category for this token **/ tokentype tk_type_ = tokentype::tk_invalid; @@ -327,7 +331,25 @@ namespace xo { return retval; } /*f64_value*/ - } /*Namespace tok*/ + + template + void + token::print(std::ostream & os) const { + os << ""; + } /*print*/ + + template + inline std::ostream & + operator<< (std::ostream & os, + const token & tk) + { + tk.print(os); + return os; + } + } /*Namespace scm*/ } /*namespace xo*/ diff --git a/include/xo/tokenizer/tokenizer.hpp b/include/xo/tokenizer/tokenizer.hpp index bc5164f7..c37a4656 100644 --- a/include/xo/tokenizer/tokenizer.hpp +++ b/include/xo/tokenizer/tokenizer.hpp @@ -11,7 +11,7 @@ #include namespace xo { - namespace tok { + namespace scm { /** * Use: * @code @@ -619,7 +619,7 @@ namespace xo { return tk; } /*notify_eof*/ - } /*namespace tok*/ + } /*namespace scm*/ } /*namespace xo*/ /* end tokenizer.hpp */ diff --git a/include/xo/tokenizer/tokentype.hpp b/include/xo/tokenizer/tokentype.hpp index cd890e38..bd20e8eb 100644 --- a/include/xo/tokenizer/tokentype.hpp +++ b/include/xo/tokenizer/tokentype.hpp @@ -9,7 +9,7 @@ #include namespace xo { - namespace tok { + namespace scm { /** @enum tokentype * @brief enum to identify different schematica input token types * @@ -124,6 +124,9 @@ namespace xo { /** keyword 'in' **/ tk_in, + /** keyword 'end' **/ + tk_end, + n_tokentype /* comes last, counts #of entries */ }; /*tokentype*/ @@ -135,8 +138,7 @@ namespace xo { os << tokentype_descr(tk_type); return os; } - } /*namespace tok*/ + } /*namespace scm*/ } /*namespace xo*/ - /* end tokentype.hpp */ diff --git a/src/tokenizer/tokentype.cpp b/src/tokenizer/tokentype.cpp index 228790ec..08dbba84 100644 --- a/src/tokenizer/tokentype.cpp +++ b/src/tokenizer/tokentype.cpp @@ -6,7 +6,7 @@ #include "tokentype.hpp" namespace xo { - namespace tok { + namespace scm { char const * tokentype_descr(tokentype tk_type) { @@ -18,27 +18,33 @@ namespace xo { CASE(tk_string); CASE(tk_symbol); CASE(tk_leftparen); + CASE(tk_rightparen); CASE(tk_leftbracket); CASE(tk_rightbracket); CASE(tk_leftbrace); CASE(tk_rightbrace); + CASE(tk_leftangle); CASE(tk_rightangle); CASE(tk_dot); CASE(tk_comma); CASE(tk_colon); + CASE(tk_doublecolon); CASE(tk_semicolon); CASE(tk_singleassign); CASE(tk_assign); CASE(tk_yields); + CASE(tk_type); CASE(tk_def); CASE(tk_lambda); CASE(tk_if); CASE(tk_let); + CASE(tk_in); + CASE(tk_end); case tokentype::tk_invalid: case tokentype::n_tokentype: @@ -49,7 +55,7 @@ namespace xo { return "???"; } /*tokentype_descr*/ - } /*namespace tok*/ + } /*namespace scm*/ } /*namespace xo*/ diff --git a/utest/token.test.cpp b/utest/token.test.cpp index 16b30399..160420b0 100644 --- a/utest/token.test.cpp +++ b/utest/token.test.cpp @@ -8,8 +8,8 @@ #include namespace xo { - using token = xo::tok::token; - using xo::tok::tokentype; + using token = xo::scm::token; + using xo::scm::tokentype; namespace ut { struct testcase_i64 { diff --git a/utest/tokenizer.test.cpp b/utest/tokenizer.test.cpp index cb796f47..03cd71ad 100644 --- a/utest/tokenizer.test.cpp +++ b/utest/tokenizer.test.cpp @@ -7,9 +7,9 @@ #include namespace xo { - using xo::tok::tokentype; - using token = xo::tok::token; - using xo::tok::span; + using xo::scm::tokentype; + using token = xo::scm::token; + using xo::scm::span; namespace ut { namespace { @@ -111,7 +111,7 @@ namespace xo { INFO(xtag("i_tc", i_tc)); using tokenizer - = xo::tok::tokenizer; + = xo::scm::tokenizer; tokenizer tkz; tokenizer::span_type From d522d3689979523342a3301f2375db9b515c46b2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:51:38 -0400 Subject: [PATCH 1285/2524] xo-tokenizer: doc: + README --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..3f0befba --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# schematica tokenizer library + +## Getting Started + +### build + install 'xo-cmake` dependency + +- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) + +Installs a few cmake ingredients, along with a build assistant `xo-build` for XO projects such as this one. + +### build + install other required XO dependencies +``` +$ xo-build --clone --configure --build --install xo-indentlog +$ xo-build --clone --configure --build --install xo-refnct +$ xo-build --clone --configure --build --install xo-subsys +$ xo-build --clone --configure --build --install xo-reflectutil +``` + +Note: can use `-n` to dry-run here + +### copy `xo-tokenizer` repository locally +``` +$ xo-build --clone xo-tokenizer +``` + +or equivalently +``` +$ git clone git@github.com:Rconybea/xo-tokenizer.git +``` + +### build + install `xo-tokenizer` + +``` +$ xo-build --configure --build --install xo-tokenizer +``` + +or equivalently: + +``` +$ PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-tokenizer -B xo-tokenizer/.build +$ cmake --build xo-tokenizer/.build +$ cmake --install xo-tokenizer/.build +``` + +### build for unit test coverage +``` +$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_INSTALL_PREFIX=$PREFIX xo-tokenizer/.build-ccov +$ cmake --build xo-tokenizer/.build-ccov +``` + +### LSP support +``` +$ cd xo-tokenizer +$ ln -s .build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` From 5ed786adc022094f8c6510e505d915b416868fb1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:51:47 -0400 Subject: [PATCH 1286/2524] xo-tokenizer: fix: missing return statement --- include/xo/tokenizer/tokenizer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/tokenizer/tokenizer.hpp b/include/xo/tokenizer/tokenizer.hpp index c37a4656..5069f17b 100644 --- a/include/xo/tokenizer/tokenizer.hpp +++ b/include/xo/tokenizer/tokenizer.hpp @@ -69,7 +69,7 @@ namespace xo { /** true if tokenizer contains stored prefix of * possibly-incomplete token **/ - bool has_prefix() const { !prefix_.empty(); } + bool has_prefix() const { return !prefix_.empty(); } /** assemble token from text @p token_text **/ From 179215e6516fc4d7cf9839b2b0dc8389e76b743c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:54:17 -0400 Subject: [PATCH 1287/2524] xo-parser: drop unused exprirtype debris --- src/parser/parser.cpp | 55 ------------------------------------------- 1 file changed, 55 deletions(-) diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 79c9b73f..8a60bbbd 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -19,33 +19,6 @@ namespace xo { using xo::reflect::TypeDescr; namespace scm { -#ifdef OBSOLETE - const char * - exprirtype_descr(exprirtype x) { - switch(x) { - case exprirtype::invalid: - return "?invalid"; - case exprirtype::empty: - return "empty"; - case exprirtype::symbol: - return "symbol"; - case exprirtype::typedescr: - return "typedescr"; - case exprirtype::n_exprirtype: - break; - } - - return "???exprirtype"; - } - - void - exprir::print(std::ostream & os) const { - os << ""; - } -#endif - const char * exprstatetype_descr(exprstatetype x) { switch(x) { @@ -76,34 +49,6 @@ namespace xo { return "???"; } -#ifdef OBSOLETE - const char * - expractiontype_descr(expractiontype x) { - switch(x) { - case expractiontype::invalid: - return "?invalid"; - case expractiontype::keep: - return "keep"; - case expractiontype::n_expractiontype: - break; - } - - return "???"; - } - - expraction - expraction::keep() { - return expraction(expractiontype::keep); - } - - void - expraction::print(std::ostream & os) const { - os << ""; - } -#endif - bool exprstate::admits_definition() const { switch(exs_type_) { From 7e088e02621b3472bb92d22f93e83c4f1512bab2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:55:52 -0400 Subject: [PATCH 1288/2524] xo-jit: fix: xo::ref::rp -> xo::rp --- include/xo/jit/IrPipeline.hpp | 4 ++-- include/xo/jit/LlvmContext.hpp | 2 +- include/xo/jit/MachPipeline.hpp | 8 ++++---- src/jit/IrPipeline.cpp | 2 +- src/jit/LlvmContext.cpp | 2 +- src/jit/MachPipeline.cpp | 6 ++++-- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/include/xo/jit/IrPipeline.hpp b/include/xo/jit/IrPipeline.hpp index d8234e6b..5268d088 100644 --- a/include/xo/jit/IrPipeline.hpp +++ b/include/xo/jit/IrPipeline.hpp @@ -51,7 +51,7 @@ namespace xo { **/ class IrPipeline : public ref::Refcount { public: - explicit IrPipeline(ref::rp llvm_cx); + explicit IrPipeline(rp llvm_cx); void run_pipeline(llvm::Function & fn); @@ -59,7 +59,7 @@ namespace xo { // ----- transforms (also adapted from kaleidescope.cpp) ------ /** keepalive for contained llvm::LLVMContext **/ - ref::rp llvm_cx_; + rp llvm_cx_; /** manages all the passes+analaysis (?) **/ std::unique_ptr llvm_fpmgr_; diff --git a/include/xo/jit/LlvmContext.hpp b/include/xo/jit/LlvmContext.hpp index cf594c82..ac577b9e 100644 --- a/include/xo/jit/LlvmContext.hpp +++ b/include/xo/jit/LlvmContext.hpp @@ -24,7 +24,7 @@ namespace xo { **/ class LlvmContext : public ref::Refcount { public: - static xo::ref::rp make(); + static rp make(); llvm::LLVMContext & llvm_cx_ref() { return *llvm_cx_; } std::unique_ptr & llvm_cx() { return llvm_cx_; } diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 11589e8c..d6ae4ea2 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -67,7 +67,7 @@ namespace xo { public: /* tracking KaleidoscopeJIT::Create() here.. */ static llvm::Expected> make_aux(); - static xo::ref::rp make(); + static rp make(); // ----- access ----- @@ -192,7 +192,7 @@ namespace xo { * jit_->addModule() * Note that this makes the module itself unavailable to us **/ - xo::ref::rp ir_pipeline_; + rp ir_pipeline_; /** owns + manages core "global" llvm data, * including type- and constant- unique-ing tables. @@ -200,7 +200,7 @@ namespace xo { * Not threadsafe, but ok to have multiple threads, * each with its own LLVMContext **/ - ref::rp llvm_cx_; + rp llvm_cx_; /** builder for intermediate-representation objects **/ std::unique_ptr> llvm_toplevel_ir_builder_; @@ -213,7 +213,7 @@ namespace xo { std::unique_ptr llvm_module_; /** map global names to functions/variables **/ - ref::rp global_env_; + rp global_env_; /** map variable names (formal parameters) to * corresponding llvm IR. diff --git a/src/jit/IrPipeline.cpp b/src/jit/IrPipeline.cpp index 285edbe3..cefd9766 100644 --- a/src/jit/IrPipeline.cpp +++ b/src/jit/IrPipeline.cpp @@ -4,7 +4,7 @@ namespace xo { namespace jit { - IrPipeline::IrPipeline(ref::rp llvm_cx) + IrPipeline::IrPipeline(rp llvm_cx) { using std::make_unique; diff --git a/src/jit/LlvmContext.cpp b/src/jit/LlvmContext.cpp index 19349aca..1e4ad007 100644 --- a/src/jit/LlvmContext.cpp +++ b/src/jit/LlvmContext.cpp @@ -4,7 +4,7 @@ namespace xo { namespace jit { - xo::ref::rp + rp LlvmContext::make() { return new LlvmContext(); } diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 9c6bb1cd..15852aee 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -60,7 +60,7 @@ namespace xo { )); } /*make*/ - xo::ref::rp + rp MachPipeline::make() { static llvm::ExitOnError llvm_exit_on_err; @@ -729,7 +729,9 @@ namespace xo { /* generate function body */ - auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); + auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "entry", + llvm_fn); ir_builder.SetInsertPoint(block); From ae59fa342828a1912fe9bad47c6195fc852a517d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:56:41 -0400 Subject: [PATCH 1289/2524] xo-jit: attach global env to lambda --- src/jit/MachPipeline.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 15852aee..1d85a438 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -714,6 +714,9 @@ namespace xo { global_env_->require_global(lambda->name(), lambda.get()); + /* correct PROVIDED this is a toplevel lambda */ + lambda->attach_envs(this->global_env_); + /* do we already know a function with this name? */ auto * llvm_fn = llvm_module_->getFunction(lambda->name()); From b88eb68547e886e042f77e672652452ceeb56b48 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:58:21 -0400 Subject: [PATCH 1290/2524] xo-jit: + wip comment --- src/jit/MachPipeline.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 1d85a438..22378c7b 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -975,6 +975,17 @@ namespace xo { * generate code for it too */ + /* WIP. STRATEGY: + * - xo::ast::ClosureExpr (an expression that generates a closure) + * closure = {lambda, env} + * + * - pass 1: + * return list of closure expressions; + * codegen the lambda decls using lambda from each closure + * - pass 2: + * codegen closures: use env chain to resolve variables + */ + /* Pass 1. */ auto fn_v = this->find_lambdas(expr); From ca19c65b02b2e4bd14438b38df9b45b0992701ae Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 15:04:34 -0400 Subject: [PATCH 1291/2524] xo-expression: fix: ns xo::ref::rp -> xo::rp --- include/xo/expression/Apply.hpp | 26 ++++++------- include/xo/expression/Constant.hpp | 6 +-- include/xo/expression/Expression.hpp | 4 +- include/xo/expression/GlobalEnv.hpp | 4 +- include/xo/expression/IfExpr.hpp | 40 ++++++++++---------- include/xo/expression/Lambda.hpp | 30 +++++++-------- include/xo/expression/LocalEnv.hpp | 10 ++--- include/xo/expression/PrimitiveInterface.hpp | 2 +- include/xo/expression/Variable.hpp | 10 ++--- src/expression/Apply.cpp | 2 - src/expression/IfExpr.cpp | 2 - src/expression/Lambda.cpp | 9 ++--- 12 files changed, 70 insertions(+), 75 deletions(-) diff --git a/include/xo/expression/Apply.hpp b/include/xo/expression/Apply.hpp index e6de3ac4..f06b8b8b 100644 --- a/include/xo/expression/Apply.hpp +++ b/include/xo/expression/Apply.hpp @@ -25,16 +25,16 @@ namespace xo { public: /** create new apply-expression instance **/ - static ref::rp make(const ref::rp & fn, - const std::vector> & argv); + static rp make(const rp & fn, + const std::vector> & argv); /** downcast from Expression **/ static ref::brw from(ref::brw x) { return ref::brw::from(x); } - const ref::rp & fn() const { return fn_; } - const std::vector> & argv() const { return argv_; } + const rp & fn() const { return fn_; } + const std::vector> & argv() const { return argv_; } virtual std::set get_free_variables() const override { std::set retval = fn_->get_free_variables(); @@ -76,7 +76,7 @@ namespace xo { return n; } - virtual ref::rp xform_layer(TransformFn xform_fn) override { + virtual rp xform_layer(TransformFn xform_fn) override { this->fn_ = fn_->xform_layer(xform_fn); for (auto & arg : argv_) @@ -96,17 +96,17 @@ namespace xo { private: Apply(TypeDescr apply_valuetype, - const ref::rp & fn, - const std::vector> & argv) + const rp & fn, + const std::vector> & argv) : Expression(exprtype::apply, apply_valuetype), fn_{fn}, argv_(argv) {} private: /** function to invoke **/ - ref::rp fn_; + rp fn_; /** argument expressions, in l-to-r order **/ - std::vector> argv_; + std::vector> argv_; }; /*Apply*/ #ifdef NOT_USING @@ -136,10 +136,10 @@ namespace xo { #endif /* reminder: initializer-lists are compile-time only */ - inline ref::rp - make_apply(const ref::rp & fn, - const std::initializer_list> args) { - std::vector> argv(args); + inline rp + make_apply(const rp & fn, + const std::initializer_list> args) { + std::vector> argv(args); return Apply::make(fn, argv); } /*make_apply*/ diff --git a/include/xo/expression/Constant.hpp b/include/xo/expression/Constant.hpp index 3ecf1fe3..77098a5b 100644 --- a/include/xo/expression/Constant.hpp +++ b/include/xo/expression/Constant.hpp @@ -30,7 +30,7 @@ namespace xo { public: /** create constant expression representing literal value x **/ - static ref::rp make(const T & x) { + static rp make(const T & x) { TypeDescr x_valuetype = Reflect::require(); return new Constant(x_valuetype, x); @@ -61,7 +61,7 @@ namespace xo { return 1; } - virtual ref::rp xform_layer(TransformFn xform_fn) override { + virtual rp xform_layer(TransformFn xform_fn) override { return xform_fn(this); } @@ -89,7 +89,7 @@ namespace xo { }; /*Constant*/ template - ref::rp>> + rp>> make_constant(const T & x) { return Constant::make(x); } diff --git a/include/xo/expression/Expression.hpp b/include/xo/expression/Expression.hpp index 89a876d4..cbea3a35 100644 --- a/include/xo/expression/Expression.hpp +++ b/include/xo/expression/Expression.hpp @@ -38,7 +38,7 @@ namespace xo { using VisitFn = std::function )>; using TransformFn = std::function - (ref::brw)>; + (ref::brw)>; using TypeDescr = xo::reflect::TypeDescr; public: @@ -69,7 +69,7 @@ namespace xo { virtual std::size_t visit_layer(VisitFn visitor_fn) = 0; /** traverse ast @ref visit_preorder but do not visit Lambdas **/ - virtual ref::rp xform_layer(TransformFn visitor_fn) = 0; + virtual rp xform_layer(TransformFn visitor_fn) = 0; /** attach an environment to each lambda expression X in this subtree, * that will: diff --git a/include/xo/expression/GlobalEnv.hpp b/include/xo/expression/GlobalEnv.hpp index 010dc296..9cbf328f 100644 --- a/include/xo/expression/GlobalEnv.hpp +++ b/include/xo/expression/GlobalEnv.hpp @@ -14,7 +14,7 @@ namespace xo { class GlobalEnv : public Environment { public: /** create instance. Probably only need one of these **/ - static ref::rp make() { return new GlobalEnv(); } + static rp make() { return new GlobalEnv(); } ref::brw require_global(const std::string & vname, ref::brw expr) { @@ -51,7 +51,7 @@ namespace xo { /* for assignable globals, need to allocate memory * addresses for these. */ - std::map> global_map_; + std::map> global_map_; }; } /*namespace ast*/ } /*namespace xo*/ diff --git a/include/xo/expression/IfExpr.hpp b/include/xo/expression/IfExpr.hpp index bfe1e116..0f62f86d 100644 --- a/include/xo/expression/IfExpr.hpp +++ b/include/xo/expression/IfExpr.hpp @@ -25,18 +25,18 @@ namespace xo { * @p when_true or @p when_false, depending on result * of evaluating expression @p test **/ - static ref::rp make(const ref::rp & test, - const ref::rp & when_true, - const ref::rp & when_false); + static rp make(const rp & test, + const rp & when_true, + const rp & when_false); /** downcast from Expression **/ static ref::brw from(ref::brw x) { return ref::brw::from(x); } - const ref::rp & test() const { return test_; } - const ref::rp & when_true() const { return when_true_; } - const ref::rp & when_false() const { return when_false_; } + const rp & test() const { return test_; } + const rp & when_true() const { return when_true_; } + const rp & when_false() const { return when_false_; } // ----- Expression ----- @@ -79,7 +79,7 @@ namespace xo { return n; } - virtual ref::rp xform_layer(TransformFn xform_fn) override { + virtual rp xform_layer(TransformFn xform_fn) override { this->test_ = this->test_->xform_layer(xform_fn); this->when_true_ = this->when_true_->xform_layer(xform_fn); this->when_false_= this->when_false_->xform_layer(xform_fn); @@ -113,13 +113,13 @@ namespace xo { * @p when_false else-branch; executes only when test fails **/ IfExpr(TypeDescr ifexpr_type, - const ref::rp & test, - const ref::rp & when_true, - const ref::rp & when_false) + rp test, + rp when_true, + rp when_false) : Expression(exprtype::ifexpr, ifexpr_type), - test_{test}, - when_true_{when_true}, - when_false_{when_false} {} + test_{std::move(test)}, + when_true_{std::move(when_true)}, + when_false_{std::move(when_false)} {} private: /** if: @@ -127,15 +127,15 @@ namespace xo { * * executes x; if true execute y; otherwise execute z **/ - ref::rp test_; - ref::rp when_true_; - ref::rp when_false_; + rp test_; + rp when_true_; + rp when_false_; }; /*IfExpr*/ - inline ref::rp - make_ifexpr(const ref::rp & test, - const ref::rp & when_true, - const ref::rp & when_false) + inline rp + make_ifexpr(const rp & test, + const rp & when_true, + const rp & when_false) { return IfExpr::make(test, when_true, when_false); } diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index 40083875..e5d4a84e 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -27,9 +27,9 @@ namespace xo { * @p argv Formal parameters, in left-to-right order * @p body Expression for body of this function **/ - static ref::rp make(const std::string & name, - const std::vector> & argv, - const ref::rp & body); + static rp make(const std::string & name, + const std::vector> & argv, + const rp & body); /** downcast from Expression **/ static ref::brw from(ref::brw x) { @@ -37,8 +37,8 @@ namespace xo { } const std::string & type_str() const { return type_str_; } - const std::vector> & argv() const { return local_env_->argv(); } - const ref::rp & body() const { return body_; } + const std::vector> & argv() const { return local_env_->argv(); } + const rp & body() const { return body_; } bool needs_closure_flag() const { return !free_var_set_.empty(); } @@ -77,7 +77,7 @@ namespace xo { return n; } - virtual ref::rp xform_layer(TransformFn /*xform_fn*/) override { + virtual rp xform_layer(TransformFn /*xform_fn*/) override { /* a layer is bounded by lambdas, don't enter them */ return this; } @@ -92,8 +92,8 @@ namespace xo { **/ Lambda(const std::string & name, TypeDescr lambda_type, - const ref::rp & local_env, - const ref::rp & body); + const rp & local_env, + const rp & body); /** compute free-variable set for this lambda **/ std::set calc_free_variables() const; @@ -104,7 +104,7 @@ namespace xo { * Goal is to unify variables that can use the same binding * path to determine memory location at runtime. **/ - std::map> regularize_layer_vars(); + std::map> regularize_layer_vars(); private: /** lambda name. Initially supporting only form like @@ -120,7 +120,7 @@ namespace xo { **/ std::string type_str_; /** function body **/ - ref::rp body_; + rp body_; /** free variables for this lambda **/ std::set free_var_set_; @@ -136,7 +136,7 @@ namespace xo { * - any variables appearing in nested lambdas * (whether formals or free variables) **/ - std::map> layer_var_map_; + std::map> layer_var_map_; /** all lambdas nested once inside this lambda's body **/ std::map> nested_lambda_map_; @@ -147,13 +147,13 @@ namespace xo { * when Lambda constructor runs, so we need to assign @ref local_env_ * later. **/ - ref::rp local_env_; + rp local_env_; }; /*Lambda*/ - inline ref::rp + inline rp make_lambda(const std::string & name, - const std::vector> & argv, - const ref::rp & body) + const std::vector> & argv, + const rp & body) { return Lambda::make(name, argv, body); } diff --git a/include/xo/expression/LocalEnv.hpp b/include/xo/expression/LocalEnv.hpp index ec3d4c81..d4facdba 100644 --- a/include/xo/expression/LocalEnv.hpp +++ b/include/xo/expression/LocalEnv.hpp @@ -26,12 +26,12 @@ namespace xo { public: /** named ctor idiom. Create instance with local variables per @p argv **/ - static ref::rp make(const std::vector> & argv) { + static rp make(const std::vector> & argv) { return new LocalEnv(argv); } Lambda * origin() const { return origin_; } - const std::vector> & argv() const { return argv_; } + const std::vector> & argv() const { return argv_; } int n_arg() const { return argv_.size(); } TypeDescr fn_arg(uint32_t i) const { return argv_[i]->valuetype(); } @@ -71,7 +71,7 @@ namespace xo { } private: - LocalEnv(const std::vector> & argv) + LocalEnv(const std::vector> & argv) : origin_{nullptr}, argv_(argv) {} private: @@ -85,12 +85,12 @@ namespace xo { Lambda * origin_ = nullptr; /** formal argument names **/ - std::vector> argv_; + std::vector> argv_; /** parent environment. A free variable in this lambda's * body will be resolved by referring them to @ref parent_env_. **/ - ref::rp parent_env_; + rp parent_env_; }; } /*namespace ast*/ } /*namespace xo*/ diff --git a/include/xo/expression/PrimitiveInterface.hpp b/include/xo/expression/PrimitiveInterface.hpp index 12d4c4a7..c8b0758b 100644 --- a/include/xo/expression/PrimitiveInterface.hpp +++ b/include/xo/expression/PrimitiveInterface.hpp @@ -62,7 +62,7 @@ namespace xo { return 1; } - virtual ref::rp xform_layer(TransformFn xform_fn) override { + virtual rp xform_layer(TransformFn xform_fn) override { return xform_fn(this); } diff --git a/include/xo/expression/Variable.hpp b/include/xo/expression/Variable.hpp index 7d965dea..43936f70 100644 --- a/include/xo/expression/Variable.hpp +++ b/include/xo/expression/Variable.hpp @@ -20,13 +20,13 @@ namespace xo { * identified by @p name, that can take on values * described by @p var_type. **/ - static ref::rp make(const std::string & name, - TypeDescr var_type) { + static rp make(const std::string & name, + TypeDescr var_type) { return new Variable(name, var_type); } /** return copy of x: same var, different object identity **/ - static ref::rp copy(ref::brw x) { + static rp copy(ref::brw x) { return new Variable(x->name(), x->valuetype()); } @@ -53,7 +53,7 @@ namespace xo { return 1; } - virtual ref::rp xform_layer(TransformFn xform_fn) override { + virtual rp xform_layer(TransformFn xform_fn) override { return xform_fn(this); } @@ -76,7 +76,7 @@ namespace xo { binding_path path_; }; /*Variable*/ - inline ref::rp + inline rp make_var(const std::string & name, reflect::TypeDescr var_type) { return Variable::make(name, var_type); diff --git a/src/expression/Apply.cpp b/src/expression/Apply.cpp index 12926ddb..36e2428a 100644 --- a/src/expression/Apply.cpp +++ b/src/expression/Apply.cpp @@ -4,8 +4,6 @@ #include "xo/indentlog/print/vector.hpp" namespace xo { - using xo::ref::rp; - namespace ast { rp Apply::make(const rp & fn, diff --git a/src/expression/IfExpr.cpp b/src/expression/IfExpr.cpp index 31389b60..bd5d231f 100644 --- a/src/expression/IfExpr.cpp +++ b/src/expression/IfExpr.cpp @@ -4,8 +4,6 @@ #include "xo/indentlog/print/vector.hpp" namespace xo { - using xo::ref::rp; - namespace ast { rp IfExpr::make(const rp & test, diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index cf9112cd..d18468cd 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -9,14 +9,13 @@ namespace xo { using xo::reflect::TypeDescrBase; using xo::reflect::FunctionTdxInfo; - using xo::ref::rp; using std::stringstream; namespace ast { rp Lambda::make(const std::string & name, const std::vector> & argv, - const ref::rp & body) + const rp & body) { using xo::reflect::FunctionTdx; @@ -69,7 +68,7 @@ namespace xo { return retval; } /*calc_free_variables*/ - std::map> + std::map> Lambda::regularize_layer_vars() { /* regularize local_env+body: make sure exactly one instance @@ -83,7 +82,7 @@ namespace xo { * Motivation is to unify Variables that would use the same * binding_path to resolve their runtime location. */ - std::map> var_map; + std::map> var_map; for (const auto & arg : local_env_->argv()) { /* each arg name can appear at most once @@ -96,7 +95,7 @@ namespace xo { this->body_ = (body_->xform_layer - ([&var_map](ref::brw x) -> ref::rp + ([&var_map](ref::brw x) -> rp { if (x->extype() == exprtype::variable) { ref::brw var = Variable::from(x); From 3964f72cea7d1d22e94c9d68c1ad69c03190524f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 15:10:14 -0400 Subject: [PATCH 1292/2524] xo-expression: fix: straggler ns fix in Lambda ctor --- src/expression/Lambda.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index d18468cd..039e1e1e 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -124,7 +124,7 @@ namespace xo { Lambda::Lambda(const std::string & name, TypeDescr lambda_type, const rp & local_env, - const ref::rp & body) + const rp & body) : FunctionInterface(exprtype::lambda, lambda_type), name_{name}, body_{body}, From 4738ff66b428b3f585ff9a862e208da90a8fdf36 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 15:12:15 -0400 Subject: [PATCH 1293/2524] xo-expression: straggler: ns fix in Primitive::make --- include/xo/expression/Primitive.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/xo/expression/Primitive.hpp b/include/xo/expression/Primitive.hpp index 49246d8b..653a3a83 100644 --- a/include/xo/expression/Primitive.hpp +++ b/include/xo/expression/Primitive.hpp @@ -37,10 +37,10 @@ namespace xo { using TypeDescr = xo::reflect::TypeDescr; public: - static ref::rp make(const std::string & name, - FunctionPointer fnptr, - bool explicit_symbol_def, - llvmintrinsic intrinsic) { + static rp make(const std::string & name, + FunctionPointer fnptr, + bool explicit_symbol_def, + llvmintrinsic intrinsic) { TypeDescr fn_type = Reflect::require(); return new Primitive(fn_type, name, fnptr, explicit_symbol_def, intrinsic); From e8a590b0d46eef35f4dc260424811c64b5904cb9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 15:14:28 -0400 Subject: [PATCH 1294/2524] xo-expression: fix: ns fix in Primitive make_primitive() helper --- include/xo/expression/Primitive.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/expression/Primitive.hpp b/include/xo/expression/Primitive.hpp index 653a3a83..5b1d46ac 100644 --- a/include/xo/expression/Primitive.hpp +++ b/include/xo/expression/Primitive.hpp @@ -124,7 +124,7 @@ namespace xo { /** adopt function @p x as a callable primitive function named @p name **/ template - ref::rp> + rp> make_primitive(const std::string & name, FunctionPointer x, bool explicit_symbol_def, From 9a1a419aef300afc86cc4bac86590639e81353f3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 03:20:19 -0400 Subject: [PATCH 1295/2524] xo-exprssion: + DefineExpr --- include/xo/expression/DefineExpr.hpp | 128 +++++++++++++++++++++++++++ src/expression/CMakeLists.txt | 1 + src/expression/DefineExpr.cpp | 99 +++++++++++++++++++++ 3 files changed, 228 insertions(+) create mode 100644 include/xo/expression/DefineExpr.hpp create mode 100644 src/expression/DefineExpr.cpp diff --git a/include/xo/expression/DefineExpr.hpp b/include/xo/expression/DefineExpr.hpp new file mode 100644 index 00000000..e7ffce65 --- /dev/null +++ b/include/xo/expression/DefineExpr.hpp @@ -0,0 +1,128 @@ +/* file DefineExpr.hpp + * + * author: Roland Conybeare, Jul 2024 + */ + +#pragma once + +#include "Expression.hpp" + +namespace xo { + namespace ast { + /** @class DefineExpr + * @brief Provide definition for a constant, variable or function + * + * At toplevel, introduces a new global variable. + * In a nested context, + * + * def foo = rhsexpr + * body... + * + * is equivalent to + * + * (lambda (foo) body...)(rhsexpr) + **/ + class DefineExpr : public Expression { + public: + static rp make(std::string name, + rp value); + + + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + const std::string & lhs_name() const { return lhs_name_; } + const rp & rhs() const { return rhs_; } + + std::set calc_free_variables() const; + + // ----- Expression ----- + + virtual std::set get_free_variables() const override { + return this->free_var_set_; + } + + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += rhs_->visit_preorder(visitor_fn); + + return n; + } + + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += this->rhs_->visit_layer(visitor_fn); + + return n; + } + + virtual rp xform_layer(TransformFn xform_fn) override { + this->rhs_ = this->rhs_->xform_layer(xform_fn); + + return xform_fn(this); + } + + virtual void attach_envs(ref::brw p) override { + rhs_->attach_envs(p); + } + + virtual void display(std::ostream & os) const override; + + protected: + /** + * + **/ + DefineExpr(TypeDescr rhs_valuetype, + std::string lhs_name, + rp rhs); + + protected: + /** symbol name for this definition **/ + std::string lhs_name_; + /** right-hand side of definition **/ + rp rhs_; + + /** free variables for this definition **/ + std::set free_var_set_; + }; + + /** @class DefineExprAccess + * @brief DefineExpr with writeable members. + * + * Convenient when scaffolding a parser, + * e.g. see xo-parser + **/ + class DefineExprAccess : public DefineExpr { + public: + static rp make(std::string lhs_name, + rp rhs); + static rp make_empty(); + + void assign_lhs_name(const std::string & x) { + this->lhs_name_ = x; + } + + void assign_rhs(const rp & x); + + private: + DefineExprAccess(TypeDescr rhs_valuetype, + std::string lhs_name, + rp rhs) + : DefineExpr(rhs_valuetype, + std::move(lhs_name), + std::move(rhs)) + {} + }; + + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end DefineExpr.hpp */ diff --git a/src/expression/CMakeLists.txt b/src/expression/CMakeLists.txt index 50edec01..32520c85 100644 --- a/src/expression/CMakeLists.txt +++ b/src/expression/CMakeLists.txt @@ -3,6 +3,7 @@ set(SELF_LIB xo_expression) set(SELF_SRCS Expression.cpp + DefineExpr.cpp Apply.cpp Lambda.cpp Variable.cpp diff --git a/src/expression/DefineExpr.cpp b/src/expression/DefineExpr.cpp new file mode 100644 index 00000000..1c67188c --- /dev/null +++ b/src/expression/DefineExpr.cpp @@ -0,0 +1,99 @@ +/* file DefineExpr.cpp + * + * author: Roland Conybeare + */ + +#include "DefineExpr.hpp" + +namespace xo { + namespace ast { + rp + DefineExpr::make(std::string lhs_name, + rp rhs) + { + TypeDescr rhs_valuetype = nullptr; + + if (rhs) + rhs_valuetype = rhs->valuetype(); + + return new DefineExpr(rhs_valuetype, + std::move(lhs_name), + std::move(rhs)); + } /*make*/ + + DefineExpr::DefineExpr(TypeDescr rhs_valuetype, + std::string lhs_name, + rp rhs) + : Expression(exprtype::define, rhs_valuetype), + lhs_name_{std::move(lhs_name)}, + rhs_{std::move(rhs)} + { + this->free_var_set_ = this->calc_free_variables(); + } + + std::set + DefineExpr::calc_free_variables() const + { + std::set retval; + + if (rhs_) + retval = rhs_->get_free_variables(); + + /* but remove this variable */ + if (!this->lhs_name().empty()) + retval.erase(this->lhs_name()); + + return retval; + } /*calc_free_variables*/ + + void + DefineExpr::display(std::ostream & os) const { + os << ""; + } /*display*/ + + // ----- DefineExprAccess ----- + + rp + DefineExprAccess::make(std::string lhs_name, + rp rhs) + { + TypeDescr rhs_valuetype = nullptr; + + if (rhs) + rhs_valuetype = rhs->valuetype(); + + return new DefineExprAccess(rhs_valuetype, + std::move(lhs_name), + std::move(rhs)); + } + + rp + DefineExprAccess::make_empty() + { + return new DefineExprAccess(nullptr /*rhs_valuetype*/, + "" /*lhs_name*/, + nullptr /*rhs*/); + } + + void + DefineExprAccess::assign_rhs(const rp & x) + { + assert(x); + + this->rhs_ = x; + + if (x) { + this->assign_valuetype(x->valuetype()); + } + + this->free_var_set_ = this->calc_free_variables(); + } + + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end DefineExpr.cpp */ From 0708bc75693525cd137a458c3942a4440761c8ae Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 03:20:38 -0400 Subject: [PATCH 1296/2524] xo-expression: + ConvertExpr --- include/xo/expression/ConvertExpr.hpp | 109 ++++++++++++++++++++++++++ src/expression/CMakeLists.txt | 1 + src/expression/ConvertExpr.cpp | 53 +++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 include/xo/expression/ConvertExpr.hpp create mode 100644 src/expression/ConvertExpr.cpp diff --git a/include/xo/expression/ConvertExpr.hpp b/include/xo/expression/ConvertExpr.hpp new file mode 100644 index 00000000..8c5f9b70 --- /dev/null +++ b/include/xo/expression/ConvertExpr.hpp @@ -0,0 +1,109 @@ +/* file ConvertExpr.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "Expression.hpp" + +namespace xo { + namespace ast { + /** @class Convertexpr + * @brief Convenience for automatic type conversion + * + * This is equivalent to calling a built-in primitive + * that performs the conversion. + * + * We rely on this for convenience, for example to parse + * code like + * + * def foo : i16 = 0 + **/ + class ConvertExpr : public Expression { + public: + static rp make(TypeDescr dest_type, + rp arg); + + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + const rp & arg() const { return arg_; } + + // ----- Expression ----- + + virtual std::set get_free_variables() const override; + + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += arg_->visit_preorder(visitor_fn); + + return n; + } + + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += this->arg_->visit_layer(visitor_fn); + + return n; + } + + virtual rp xform_layer(TransformFn xform_fn) override { + this->arg_ = this->arg_->xform_layer(xform_fn); + + return xform_fn(this); + } + + virtual void attach_envs(ref::brw p) override { + arg_->attach_envs(p); + } + + virtual void display(std::ostream & os) const override; + + protected: + ConvertExpr(TypeDescr dest_type, + rp arg) + : Expression(exprtype::convert, dest_type), + arg_{std::move(arg)} + {} + + protected: + /** source expression. Convert + * @c arg_->valuetype() to @c dest_type_ + **/ + rp arg_; + }; + + /** @class ConvertExprAccess + * @brief ConvertExpr with writeable members. + * + * Convenient when scaffolding a parser, + * e.g. see xo-parser + **/ + class ConvertExprAccess : public ConvertExpr { + public: + static rp make(TypeDescr dest_type, + rp arg); + static rp make_empty(); + + void assign_arg(rp arg) { this->arg_ = std::move(arg); } + + private: + ConvertExprAccess(TypeDescr dest_type, + rp arg) + : ConvertExpr(dest_type, + std::move(arg)) + {} + }; + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end ConvertExpr.hpp */ diff --git a/src/expression/CMakeLists.txt b/src/expression/CMakeLists.txt index 32520c85..eb0f6d64 100644 --- a/src/expression/CMakeLists.txt +++ b/src/expression/CMakeLists.txt @@ -9,6 +9,7 @@ set(SELF_SRCS Variable.cpp IfExpr.cpp LocalEnv.cpp + ConvertExpr.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) diff --git a/src/expression/ConvertExpr.cpp b/src/expression/ConvertExpr.cpp new file mode 100644 index 00000000..2e741f1b --- /dev/null +++ b/src/expression/ConvertExpr.cpp @@ -0,0 +1,53 @@ +/* file ConvertExpr.cpp + * + * author: Roland Conybeare + */ + +#include "ConvertExpr.hpp" + +namespace xo { + namespace ast { + rp + ConvertExpr::make(TypeDescr dest_type, + rp arg) + { + return new ConvertExpr(dest_type, + std::move(arg)); + } + + std::set + ConvertExpr::get_free_variables() const { + if (this->arg_) + return this->arg_->get_free_variables(); + else + return std::set(); + } + + void + ConvertExpr::display(std::ostream & os) const { + os << "valuetype()->short_name()) + << xtag("arg", arg_) + << ">"; + } + + // ----- ConvertExprAccess ----- + + rp + ConvertExprAccess::make(TypeDescr dest_type, + rp arg) + { + return new ConvertExprAccess(dest_type, + std::move(arg)); + } + + rp + ConvertExprAccess::make_empty() { + return new ConvertExprAccess(nullptr /*dest_type*/, + nullptr /*arg*/); + } + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end ConvertExpr.cpp */ From cad31397ec7b341b2dde9ff6de259e6377832663 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 03:22:31 -0400 Subject: [PATCH 1297/2524] xo-expression: straggler: exprtype::define --- include/xo/expression/exprtype.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/xo/expression/exprtype.hpp b/include/xo/expression/exprtype.hpp index d8e66a61..c108bb2d 100644 --- a/include/xo/expression/exprtype.hpp +++ b/include/xo/expression/exprtype.hpp @@ -22,6 +22,8 @@ namespace xo { constant, /** a literal constant that refers to a linkable named function **/ primitive, + /** variable/function definition **/ + define, /** function call **/ apply, /** function definition **/ @@ -42,6 +44,7 @@ namespace xo { case exprtype::invalid: return "?exprtype"; case exprtype::constant: return "constant"; case exprtype::primitive: return "primitive"; + case exprtype::define: return "define"; case exprtype::apply: return "apply"; case exprtype::lambda: return "lambda"; case exprtype::variable: return "variable"; From 2d0608de52bc4c534005e2032fb46b0ff1d1b33c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 03:23:07 -0400 Subject: [PATCH 1298/2524] xo-epression: straggler: exprtype::convert --- include/xo/expression/exprtype.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/xo/expression/exprtype.hpp b/include/xo/expression/exprtype.hpp index c108bb2d..49745d52 100644 --- a/include/xo/expression/exprtype.hpp +++ b/include/xo/expression/exprtype.hpp @@ -6,7 +6,7 @@ #pragma once #include -#include +//#include namespace xo { namespace ast { @@ -32,6 +32,8 @@ namespace xo { variable, /** if-then-else **/ ifexpr, + /** type conversion **/ + convert, /** not an expression. comes last, counts entries **/ n_expr @@ -49,6 +51,7 @@ namespace xo { case exprtype::lambda: return "lambda"; case exprtype::variable: return "variable"; case exprtype::ifexpr: return "if_expr"; + case exprtype::convert: return "convert"; default: break; } From fce0fea1cbdd86df5a27892e14044ad13a4179bd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 04:35:26 -0400 Subject: [PATCH 1299/2524] xo-expression: + Expression.assign_valuetype() --- include/xo/expression/Expression.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/xo/expression/Expression.hpp b/include/xo/expression/Expression.hpp index cbea3a35..3e9137c4 100644 --- a/include/xo/expression/Expression.hpp +++ b/include/xo/expression/Expression.hpp @@ -92,6 +92,10 @@ namespace xo { /** human-readable string representation **/ virtual std::string display_string() const; + protected: + /** useful when scaffolding expressions in a parser **/ + void assign_valuetype(TypeDescr x) { valuetype_ = x; } + private: /** expression type (constant | apply | ..) for this expression **/ exprtype extype_ = exprtype::invalid; From 99c1ebc7fb4ad93b0c73488b839544ae6cafca93 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 04:40:56 -0400 Subject: [PATCH 1300/2524] xo-reader: + reader.hpp --- include/xo/parser/reader.hpp | 56 ++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 include/xo/parser/reader.hpp diff --git a/include/xo/parser/reader.hpp b/include/xo/parser/reader.hpp new file mode 100644 index 00000000..39dcd6b8 --- /dev/null +++ b/include/xo/parser/reader.hpp @@ -0,0 +1,56 @@ +/* file reader.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "parser.hpp" +#include "xo/tokenizer/tokenizer.hpp" + +namespace xo { + namespace scm { + /** + * Use: + * @code + * reader rdr; + * + * bool eof = false; + * while (!eof) { + * auto input = ins.read_some(); + * // eof: true if no more input will be forthcoming from this stream + * eof = ins.eof(); + * + * for (auto rem = input; ; !rem.empty()) { + * // res: (parsed-expr, used) + * auto res = rdr.read_expr(rem, eof); + * + * if (res.first) { + * // do something with res.first (parsed expr) + * ... + * } + * + * rem = rem.suffix_after(res.second); + * } + * } + * + * // expect !rdr.has_prefix() + * + * @endcode + **/ + class reader { + public: + reader() = default; + + private: + /** tokenizer: text -> tokens **/ + tokenizer tokenizer_; + + /** parser: tokens -> expressions (TODO: reanme ->reader) **/ + parser parser_; + }; + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end Repl.hpp */ From f58502c8a83406081ab5c5e1ac790295824f2f5e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 04:41:50 -0400 Subject: [PATCH 1301/2524] xo-reader: trivial: comment tidy --- include/xo/parser/parser.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index e4e8943a..4245c2c3 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -286,7 +286,7 @@ namespace xo { * arg-expr(i) = expression * * if-expr = if (test-expr) then-block else else-block - * | (test-expr) ? then-expr : else-expr + * | ((test-expr) ? then-expr : else-expr) * test-expr = expression * then-block = block * else-block = block From ec7d50f052589e44da651c447f8c87ef5b4a41a2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 09:33:03 -0400 Subject: [PATCH 1302/2524] xo-tokenizer: feat: + span::operator+= --- include/xo/tokenizer/span.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/xo/tokenizer/span.hpp b/include/xo/tokenizer/span.hpp index 36353a63..00f8cf74 100644 --- a/include/xo/tokenizer/span.hpp +++ b/include/xo/tokenizer/span.hpp @@ -93,6 +93,16 @@ namespace xo { /** @brief report the number of elements (of type CharT) in this span. **/ size_type size() const { return hi_ - lo_; } + span & operator+=(const span & x) { + if (hi_ == x.lo_) { + hi_ = x.hi_; + } else { + assert(false); + } + + return *this; + } + /** print representation for this span on stream @p os **/ void print(std::ostream & os) const { os << " Date: Tue, 6 Aug 2024 09:33:36 -0400 Subject: [PATCH 1303/2524] xo-tokenizer: doc: ++ comments --- include/xo/tokenizer/tokenizer.hpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/include/xo/tokenizer/tokenizer.hpp b/include/xo/tokenizer/tokenizer.hpp index 5069f17b..bc0507e0 100644 --- a/include/xo/tokenizer/tokenizer.hpp +++ b/include/xo/tokenizer/tokenizer.hpp @@ -22,7 +22,7 @@ namespace xo { * span_type input = ...; * * while !input.empty() { - * auto res = tkz.assemble_scan(input); + * auto res = tkz.scan(input); * const auto & tk = res.first; * * // do something with tk if tk.is_valid() @@ -75,7 +75,12 @@ namespace xo { **/ token_type assemble_token(const span_type & token_text) const; - /** scan for next input token, given @p input **/ + /** scan for next input token, given @p input. + * Note tokenizer can consume input (e.g. whitespace) + * without completing a token + * + * @return {parsed token, consumed span} + **/ scan_result scan(const span_type & input); /** notify end of input, resolve any stored input **/ @@ -568,7 +573,7 @@ namespace xo { } if (!complete_flag) { - /* need more input to know if/when tokken complete */ + /* need more input to know if/when token complete */ this->prefix_ += std::string(tk_start, input.hi()); log && log(xtag("captured-prefix", this->prefix_)); From dabc642ca4f4d0b9f3b4a4276d48984c26ac3f78 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 09:33:53 -0400 Subject: [PATCH 1304/2524] xo-tokenizer: feat: + tokenizer.scan2() --- include/xo/tokenizer/tokenizer.hpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/include/xo/tokenizer/tokenizer.hpp b/include/xo/tokenizer/tokenizer.hpp index bc0507e0..7f34b2ad 100644 --- a/include/xo/tokenizer/tokenizer.hpp +++ b/include/xo/tokenizer/tokenizer.hpp @@ -83,6 +83,12 @@ namespace xo { **/ scan_result scan(const span_type & input); + /** When eof is false, same as scan(input). + * When eof is true and scan(input) does not report a token, + * return notify_eof() + **/ + scan_result scan2(const span_type & input, bool eof); + /** notify end of input, resolve any stored input **/ token_type notify_eof(); @@ -607,6 +613,18 @@ namespace xo { { tk, input.prefix(whitespace.size() + token_span.size()) }; } /*scan*/ + template + auto + tokenizer::scan2(const span_type & input, bool eof) -> scan_result { + auto sr = this->scan(input); + + if (!sr.first.is_valid() && eof) { + sr.first = this->notify_eof(); + } + + return sr; + } + template auto tokenizer::notify_eof() -> token_type { From a4848044ea9165c60441120d8caccf6ed6009d50 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 09:42:38 -0400 Subject: [PATCH 1305/2524] xo-parser: feat: + parser.has_incomplete_expr() --- include/xo/parser/parser.hpp | 6 ++++++ src/parser/parser.cpp | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/include/xo/parser/parser.hpp b/include/xo/parser/parser.hpp index e4e8943a..deabaab8 100644 --- a/include/xo/parser/parser.hpp +++ b/include/xo/parser/parser.hpp @@ -327,6 +327,12 @@ namespace xo { return exprstatetype::invalid; } + /** true iff parser contains state for an incomplete expression. + * For this to be true, parser must have consumed at least one token + * since end of last toplevel expression + **/ + bool has_incomplete_expr() const; + /** put parser into state for beginning of a translation unit * (i.e. input stream) **/ diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 8a60bbbd..71da1798 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -689,6 +689,11 @@ namespace xo { // ----- parser ----- + bool + parser::has_incomplete_expr() const { + return !xs_stack_.empty(); + } + void parser::begin_translation_unit() { xs_stack_.push_exprstate From 07ba966e083f7f4d20d7615db04288a842b2a09d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 09:42:53 -0400 Subject: [PATCH 1306/2524] xo-parser: appease g++: return statement after switch --- src/parser/parser.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 71da1798..9327f893 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -76,6 +76,8 @@ namespace xo { /* unreachable */ return false; } + + return false; } bool @@ -105,6 +107,8 @@ namespace xo { /* unreachable */ return false; } + + return false; } bool @@ -133,6 +137,8 @@ namespace xo { /* unreachable */ return false; } + + return false; } bool @@ -181,6 +187,8 @@ namespace xo { /* unreachable */ return false; } + + return false; } bool @@ -206,6 +214,8 @@ namespace xo { /* unreachable */ return false; } + + return false; } void From 7e311ab0cbbfbec84843a814d1637ce0c39a57e1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 09:43:32 -0400 Subject: [PATCH 1307/2524] xo-parser: + reader class (tokenizer -> parser pipeline) --- include/xo/parser/reader.hpp | 41 +++++++++++++++++--- src/parser/CMakeLists.txt | 3 +- src/parser/reader.cpp | 75 ++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 src/parser/reader.cpp diff --git a/include/xo/parser/reader.hpp b/include/xo/parser/reader.hpp index 39dcd6b8..b36489d6 100644 --- a/include/xo/parser/reader.hpp +++ b/include/xo/parser/reader.hpp @@ -6,10 +6,27 @@ #pragma once #include "parser.hpp" +#include "xo/expression/Expression.hpp" #include "xo/tokenizer/tokenizer.hpp" namespace xo { namespace scm { + /** @class parse_result + * @brief Result object returned from reader::read_expr + **/ + struct reader_result { + using Expression = xo::ast::Expression; + using span_type = span; + + /** parsed schematica expression **/ + rp expr_; + /** span giving text input consumed to construct expr, + * including any leading whitespace. + * This is the span returned in result of tokenizer::scan() + **/ + span_type rem_; + }; + /** * Use: * @code @@ -21,7 +38,7 @@ namespace xo { * // eof: true if no more input will be forthcoming from this stream * eof = ins.eof(); * - * for (auto rem = input; ; !rem.empty()) { + * for (auto rem = input; !rem.empty();) { * // res: (parsed-expr, used) * auto res = rdr.read_expr(rem, eof); * @@ -39,18 +56,32 @@ namespace xo { * @endcode **/ class reader { + public: + using tokenizer_type = tokenizer; + using span_type = tokenizer_type::span_type; + public: reader() = default; + /** Try to read one expression from @p input. + * Return struct containing parsed expression + * and span of characters comprising that expression + * + * @param input Supply this input span of chars + * @param eof. True if input stream supplying @p input + * reports end-of-file immediately after the last char + * in @p input. + **/ + reader_result read_expr(const span_type & input, bool eof); + private: /** tokenizer: text -> tokens **/ - tokenizer tokenizer_; + tokenizer_type tokenizer_; - /** parser: tokens -> expressions (TODO: reanme ->reader) **/ + /** parser: tokens -> expressions **/ parser parser_; }; } /*namespace scm*/ } /*namespace xo*/ - -/* end Repl.hpp */ +/* end reader.hpp */ diff --git a/src/parser/CMakeLists.txt b/src/parser/CMakeLists.txt index 2af43636..a97b50fa 100644 --- a/src/parser/CMakeLists.txt +++ b/src/parser/CMakeLists.txt @@ -2,7 +2,8 @@ set(SELF_LIB xo_parser) set(SELF_SRCS - parser.cpp) + parser.cpp + reader.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_dependency(${SELF_LIB} xo_expression) diff --git a/src/parser/reader.cpp b/src/parser/reader.cpp new file mode 100644 index 00000000..84d00b30 --- /dev/null +++ b/src/parser/reader.cpp @@ -0,0 +1,75 @@ +/* @file reader.cpp */ + +#include "reader.hpp" + +namespace xo { + namespace scm { + reader_result + reader::read_expr(const span_type & input_arg, bool eof) + { + span_type input = input_arg; + + /* input text-span consumed by this call. + * Always comprises some number (possibly 0) + * of complete tokens, along with any leading + * whitespace + */ + span_type expr_span = input.prefix(0ul); + + while (!input.empty()) { + /* read one token from input */ + auto sr = this->tokenizer_.scan2(input, eof); + const auto & tk = sr.first; + const span_type & used_span = sr.second; + + input = input.after_prefix(used_span); + expr_span += used_span; + + if (tk.is_valid()) { + /* forward just-read token to parser */ + auto expr = this->parser_.include_token(tk); + + if (expr) { + /* token completes an expression -> victory */ + return reader_result(expr, expr_span); + } else { + /* token did not complete an expression + * (e.g. token for '[') + * + * input span may contain more tokens -> iterate + */ + input = input.after_prefix(used_span); + } + } else { + assert(input.empty()); + + /* no more tokens in input */ + break; + } + } + + /* control here: either + * 1. input.empty (perhaps ate some whitespace, ok) + * 2. missing or incomplete token (ok unless eof) + */ + if (eof) { + if (parser_.has_incomplete_expr()) { + throw std::runtime_error + ("reader::read_expr" + ": eof reached with incomplete expression"); + } + + if (tokenizer_.has_prefix()) { + throw std::runtime_error + ("reader::read_expr" + ": unintelligible input recognized at eof"); + } + } + + return reader_result(nullptr, expr_span); + } + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end reader.cpp */ From 0fdef0f3173f5fbf0f2209486387b06ffdf13aea Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 10:11:36 -0400 Subject: [PATCH 1308/2524] xo-reader: tidy: + begin/end translation unit methods --- include/xo/parser/reader.hpp | 17 +++++++++++++++++ src/parser/reader.cpp | 10 ++++++++++ 2 files changed, 27 insertions(+) diff --git a/include/xo/parser/reader.hpp b/include/xo/parser/reader.hpp index b36489d6..4009f722 100644 --- a/include/xo/parser/reader.hpp +++ b/include/xo/parser/reader.hpp @@ -31,6 +31,7 @@ namespace xo { * Use: * @code * reader rdr; + * rdr.begin_translation_unit() * * bool eof = false; * while (!eof) { @@ -63,6 +64,22 @@ namespace xo { public: reader() = default; + /** call once before calling .read_expr(): + * 1. with new reader + * 2. if last read_expr() call had eof=true + **/ + void begin_translation_unit(); + + /** counterpart to .begin_translation_unit(), + * provided for symmetry's sake + * + * Equivalent to: + * @code + * read_expr(span_type(nullptr, nullptr), true); + * @endcode + **/ + reader_result end_translation_unit(); + /** Try to read one expression from @p input. * Return struct containing parsed expression * and span of characters comprising that expression diff --git a/src/parser/reader.cpp b/src/parser/reader.cpp index 84d00b30..c24ebf94 100644 --- a/src/parser/reader.cpp +++ b/src/parser/reader.cpp @@ -4,6 +4,16 @@ namespace xo { namespace scm { + void + reader::begin_translation_unit() { + parser_.begin_translation_unit(); + } + + reader_result + reader::end_translation_unit() { + return this->read_expr(span_type(nullptr, nullptr), true /*eof*/); + } + reader_result reader::read_expr(const span_type & input_arg, bool eof) { From f00c390e37f3d90022ad439721e0d6e2a91d9028 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 10:19:33 -0400 Subject: [PATCH 1309/2524] xo-reader: naming: xo_parser->xo_reader --- CMakeLists.txt | 6 +++--- .../{xo_parserConfig.cmake.in => xo_readerConfig.cmake.in} | 0 include/xo/{parser => reader}/parser.hpp | 0 include/xo/{parser => reader}/reader.hpp | 0 src/{parser => reader}/CMakeLists.txt | 2 +- src/{parser => reader}/parser.cpp | 0 src/{parser => reader}/reader.cpp | 0 utest/CMakeLists.txt | 6 +++--- utest/parser.test.cpp | 2 +- utest/{parser_utest_main.cpp => reader_utest_main.cpp} | 0 10 files changed, 8 insertions(+), 8 deletions(-) rename cmake/{xo_parserConfig.cmake.in => xo_readerConfig.cmake.in} (100%) rename include/xo/{parser => reader}/parser.hpp (100%) rename include/xo/{parser => reader}/reader.hpp (100%) rename src/{parser => reader}/CMakeLists.txt (91%) rename src/{parser => reader}/parser.cpp (100%) rename src/{parser => reader}/reader.cpp (100%) rename utest/{parser_utest_main.cpp => reader_utest_main.cpp} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 84eccb39..7748dc4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ -# xo-parser/CMakeLists.txt +# xo-reader/CMakeLists.txt cmake_minimum_required(VERSION 3.10) -project(xo_parser VERSION 0.1) +project(xo_reader VERSION 0.1) include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) @@ -18,7 +18,7 @@ add_definitions(${PROJECT_CXX_FLAGS}) # ---------------------------------------------------------------- -add_subdirectory(src/parser) +add_subdirectory(src/reader) add_subdirectory(utest) # ---------------------------------------------------------------- diff --git a/cmake/xo_parserConfig.cmake.in b/cmake/xo_readerConfig.cmake.in similarity index 100% rename from cmake/xo_parserConfig.cmake.in rename to cmake/xo_readerConfig.cmake.in diff --git a/include/xo/parser/parser.hpp b/include/xo/reader/parser.hpp similarity index 100% rename from include/xo/parser/parser.hpp rename to include/xo/reader/parser.hpp diff --git a/include/xo/parser/reader.hpp b/include/xo/reader/reader.hpp similarity index 100% rename from include/xo/parser/reader.hpp rename to include/xo/reader/reader.hpp diff --git a/src/parser/CMakeLists.txt b/src/reader/CMakeLists.txt similarity index 91% rename from src/parser/CMakeLists.txt rename to src/reader/CMakeLists.txt index a97b50fa..687ae4e1 100644 --- a/src/parser/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -1,6 +1,6 @@ # parser/CMakeLists.txt -set(SELF_LIB xo_parser) +set(SELF_LIB xo_reader) set(SELF_SRCS parser.cpp reader.cpp) diff --git a/src/parser/parser.cpp b/src/reader/parser.cpp similarity index 100% rename from src/parser/parser.cpp rename to src/reader/parser.cpp diff --git a/src/parser/reader.cpp b/src/reader/reader.cpp similarity index 100% rename from src/parser/reader.cpp rename to src/reader/reader.cpp diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index d70b7d4d..d1e00b46 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -1,13 +1,13 @@ -# xo-parser/utest/CMakeLists.txt +# xo-reader/utest/CMakeLists.txt set(UTEST_EXE utest.parser) set(UTEST_SRCS - parser_utest_main.cpp + reader_utest_main.cpp parser.test.cpp) if (ENABLE_TESTING) xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS}) - xo_self_dependency(${UTEST_EXE} xo_parser) + xo_self_dependency(${UTEST_EXE} xo_reader) #xo_dependency(${UTEST_EXE} xo_ratio) #xo_dependency(${UTEST_EXE} xo_reflectutil) xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2) diff --git a/utest/parser.test.cpp b/utest/parser.test.cpp index 8397f903..aa230c7a 100644 --- a/utest/parser.test.cpp +++ b/utest/parser.test.cpp @@ -3,7 +3,7 @@ * author: Roland Conybeare */ -#include "xo/parser/parser.hpp" +#include "xo/reader/parser.hpp" #include namespace xo { diff --git a/utest/parser_utest_main.cpp b/utest/reader_utest_main.cpp similarity index 100% rename from utest/parser_utest_main.cpp rename to utest/reader_utest_main.cpp From f57d23fef04009f03e329601e1eecafb01b79d5e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 10:36:21 -0400 Subject: [PATCH 1310/2524] xo-tokenizer: feat: + span.from_cstr() --- include/xo/tokenizer/span.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/xo/tokenizer/span.hpp b/include/xo/tokenizer/span.hpp index 00f8cf74..e53bc18e 100644 --- a/include/xo/tokenizer/span.hpp +++ b/include/xo/tokenizer/span.hpp @@ -24,6 +24,14 @@ namespace xo { /** @brief create span for the contiguous memory range [@p lo, @p hi) **/ span(CharT * lo, CharT * hi) : lo_{lo}, hi_{hi} {} + /** @brief create span for C-style string @p cstr **/ + static span from_cstr(const CharT * cstr) { + CharT * lo = cstr; + CharT * hi = cstr ? cstr + strlen(cstr) : nullptr; + + return span(lo, hi); + } + ///@{ /** @name getters **/ From 91545c973259fa80e6fcbca56b8085a0e57038dd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 10:52:27 -0400 Subject: [PATCH 1311/2524] xo-reader: lint: drop unused defs --- src/reader/parser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index 9327f893..b4a9eaf9 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -12,8 +12,8 @@ namespace xo { using xo::ast::Expression; - using xo::ast::DefineExpr; - using xo::ast::ConvertExpr; + //using xo::ast::DefineExpr; + //using xo::ast::ConvertExpr; using xo::ast::Constant; using xo::reflect::Reflect; using xo::reflect::TypeDescr; From f591d9703ecca00a1a547222542d3d52b0158e58 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 10:52:47 -0400 Subject: [PATCH 1312/2524] xo-reader: tidy: fix utest exe name --- utest/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index d1e00b46..b209a9c9 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -1,6 +1,6 @@ # xo-reader/utest/CMakeLists.txt -set(UTEST_EXE utest.parser) +set(UTEST_EXE utest.reader) set(UTEST_SRCS reader_utest_main.cpp parser.test.cpp) From 876489700f12ee608b977e959046728e56a172de Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 10:53:04 -0400 Subject: [PATCH 1313/2524] xo-reader: + utest for reader [failing!] --- utest/CMakeLists.txt | 3 ++- utest/reader.test.cpp | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 utest/reader.test.cpp diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index b209a9c9..1f876ca1 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -3,7 +3,8 @@ set(UTEST_EXE utest.reader) set(UTEST_SRCS reader_utest_main.cpp - parser.test.cpp) + parser.test.cpp + reader.test.cpp) if (ENABLE_TESTING) xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS}) diff --git a/utest/reader.test.cpp b/utest/reader.test.cpp new file mode 100644 index 00000000..376dbf05 --- /dev/null +++ b/utest/reader.test.cpp @@ -0,0 +1,38 @@ +/* @file reader.test.cpp */ + +#include "xo/reader/reader.hpp" +#include + +namespace xo { + using xo::scm::reader; + + namespace ut { + TEST_CASE("reader", "[reader]") { + for (std::size_t i_tc = 0; i_tc < 1; ++i_tc) { + reader rdr; + + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag), + xtag("utest", "reader"), xtag("i_tc", i_tc)); + + rdr.begin_translation_unit(); + + try { + auto rr = rdr.read_expr(reader::span_type::from_cstr("def foo : f64 = 3.14159265"), + true /*eof*/); + + REQUIRE(rr.expr_.get()); + REQUIRE(rr.rem_.empty()); + } catch (std::exception & ex) { + log && log(ex.what()); + + INFO(ex.what()); + + REQUIRE(false); + } + } + } + } /*namespace ut*/ +} /*namespace xo*/ + +/* end reader.test.cpp */ From 5d31ac7a439c2790d9e23fdc3816067be9f19506 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 11:37:41 -0400 Subject: [PATCH 1314/2524] xo-tokenizer: mvp: recognize keywords --- include/xo/tokenizer/token.hpp | 1 + include/xo/tokenizer/tokenizer.hpp | 28 ++++++++++++++++++++++++++++ utest/tokenizer.test.cpp | 8 ++++++++ 3 files changed, 37 insertions(+) diff --git a/include/xo/tokenizer/token.hpp b/include/xo/tokenizer/token.hpp index 3883e15a..84b1a1b8 100644 --- a/include/xo/tokenizer/token.hpp +++ b/include/xo/tokenizer/token.hpp @@ -86,6 +86,7 @@ namespace xo { static token if_token() { return token(tokentype::tk_if); } static token let() { return token(tokentype::tk_let); } static token in() { return token(tokentype::tk_in); } + static token end() { return token(tokentype::tk_end); } tokentype tk_type() const { return tk_type_; } const std::string & text() const { return text_; } diff --git a/include/xo/tokenizer/tokenizer.hpp b/include/xo/tokenizer/tokenizer.hpp index 7f34b2ad..4fad37ba 100644 --- a/include/xo/tokenizer/tokenizer.hpp +++ b/include/xo/tokenizer/tokenizer.hpp @@ -510,6 +510,34 @@ namespace xo { ; /* nothing to do here -- desired tk_text already constructed */ } + if (tk_type == tokentype::tk_symbol) { + /* check for keywords */ + + bool keep_text = false; + + if (tk_text == "type") { + tk_type = tokentype::tk_type; + } else if (tk_text == "def") { + tk_type = tokentype::tk_def; + } else if (tk_text == "lambda") { + tk_type = tokentype::tk_lambda; + } else if (tk_text == "if") { + tk_type = tokentype::tk_if; + } else if (tk_text == "let") { + tk_type = tokentype::tk_let; + } else if (tk_text == "in") { + tk_type = tokentype::tk_in; + } else if (tk_text == "end") { + tk_type = tokentype::tk_end; + } else { + /* keep as symbol */ + keep_text = true; + } + + if (!keep_text) + tk_text.clear(); + } + return token_type(tk_type, std::move(tk_text)); } /*assemble_token*/ diff --git a/utest/tokenizer.test.cpp b/utest/tokenizer.test.cpp index 03cd71ad..b5d8303a 100644 --- a/utest/tokenizer.test.cpp +++ b/utest/tokenizer.test.cpp @@ -100,6 +100,14 @@ namespace xo { token::string_token("tab to the right [\t], to the right [\t]"), true}, {"symbol", false, token::symbol_token("symbol"), true}, + + {"type", false, token::type(), true}, + {"def", false, token::def(), true}, + {"lambda", false, token::lambda(), true}, + {"if", false, token::if_token(), true}, + {"let", false, token::let(), true}, + {"in", false, token::in(), true}, + {"end", false, token::end(), true}, }; } From 7c593816045729c8d854f4db54f3f4dd9ac8c5d1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 13:01:43 -0400 Subject: [PATCH 1315/2524] xo-refcnt: feat: printing for brw --- include/xo/refcnt/Refcounted.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/include/xo/refcnt/Refcounted.hpp b/include/xo/refcnt/Refcounted.hpp index a9d4386b..41abf938 100644 --- a/include/xo/refcnt/Refcounted.hpp +++ b/include/xo/refcnt/Refcounted.hpp @@ -329,6 +329,17 @@ namespace xo { return Borrow(*this); } /*borrow*/ + template + inline std::ostream & + operator<<(std::ostream & os, Borrow x) { + if (x) { + os << *x; + } else { + os << "() << ">"; + } + return os; + } /*operator<<*/ + } /*namespace ref*/ } /*namespace xo*/ From 37268113fbdc55648f446e305647bdc3ae448386 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 23:09:05 -0400 Subject: [PATCH 1316/2524] xo-parser: prep: semicolon expr separator, prep for infix ops --- include/xo/reader/parser.hpp | 92 ++++++++--- include/xo/reader/reader.hpp | 3 + src/reader/parser.cpp | 306 +++++++++++++++++++++++++++++++---- src/reader/reader.cpp | 15 +- utest/parser.test.cpp | 38 ++++- utest/reader.test.cpp | 15 +- 6 files changed, 409 insertions(+), 60 deletions(-) diff --git a/include/xo/reader/parser.hpp b/include/xo/reader/parser.hpp index 5888c9f9..2c26a8e9 100644 --- a/include/xo/reader/parser.hpp +++ b/include/xo/reader/parser.hpp @@ -30,6 +30,8 @@ namespace xo { expect_symbol, expect_type, + expr_progress, + n_exprstatetype }; @@ -58,18 +60,29 @@ namespace xo { public: exprstate() = default; exprstate(exprstatetype exs_type, - rp def_expr = nullptr) + rp candidate_expr, + rp def_expr) : exs_type_{exs_type}, + gen_expr_{std::move(candidate_expr)}, def_expr_{std::move(def_expr)} {} static exprstate expect_toplevel_expression_sequence() { - return exprstate(exprstatetype::expect_toplevel_expression_sequence); + return exprstate(exprstatetype::expect_toplevel_expression_sequence, nullptr, nullptr); } - static exprstate def_0() { - return exprstate(exprstatetype::def_0); + static exprstate expect_rhs_expression() { + return exprstate(exprstatetype::expect_rhs_expression, nullptr, nullptr); } static exprstate expect_symbol() { - return exprstate(exprstatetype::expect_symbol); + return exprstate(exprstatetype::expect_symbol, nullptr, nullptr); + } + static exprstate expect_type() { + return exprstate(exprstatetype::expect_type, nullptr, nullptr); + } + static exprstate make_expr_progress(rp expr) { + return exprstate(exprstatetype::expr_progress, expr, nullptr); + } + static exprstate def_0(rp def_expr) { + return exprstate(exprstatetype::def_0, nullptr, def_expr); } exprstatetype exs_type() const { return exs_type_; } @@ -82,8 +95,14 @@ namespace xo { bool admits_symbol() const; /** true iff this parsing state admits a colon as next token **/ bool admits_colon() const; + /** true iff this parsing state admits a semicolon as next token **/ + bool admits_semicolon() const; /** true iff this parsing state admits a singleassign '=' as next token **/ bool admits_singleassign() const; +#ifdef NOT_YET + /** true iff this parsing state admits a leftparen '(' as next token **/ + bool admits_leftparen() const; +#endif /** true iff this parsing state admits a 64-bit floating point literal token **/ bool admits_f64() const; @@ -112,16 +131,23 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr); void on_colon(exprstatestack * p_stack); + void on_semicolon(exprstatestack * p_stack, + rp * p_emit_expr); void on_singleassign(exprstatestack * p_stack); +#ifdef NOT_YET + void on_leftparen(exprstatestack * p_stack, + rp * p_emit_expr); +#endif void on_f64(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); private: /** - * def foo : f64 = 1 - * ^ ^ ^ ^ ^ ^ ^ - * | | | | | | (done) + * def foo : f64 = 1 ; + * ^ ^ ^ ^ ^ ^ ^ ^ + * | | | | | | | (done) + * | | | | | | ?? * | | | | | def_4:expect_rhs_expression * | | | | def_3 * | | | def_2:expect_type @@ -139,6 +165,8 @@ namespace xo { **/ exprstatetype exs_type_; + /** generic expression **/ + rp gen_expr_; /** scaffold a define-expression here **/ rp def_expr_; /** scafford a convert-expression here. @@ -199,10 +227,10 @@ namespace xo { * decltype point * * // forward declarations - * decl pi : f64 - * decl fib(n : i32) -> i32 + * decl pi : f64; + * decl fib(n : i32) -> i32; * - * def pi = 3.14159265 // constant. = is single assignment + * def pi = 3.14159265; // constant. = is single assignment * * def fib(n : i32) -> i32 { * // nested defs ok @@ -211,33 +239,37 @@ namespace xo { * // (n == 0) ? s1 : aux(n - 1, s1 + s2, s1) * // * if (n == 0) { - * s1 + * s1; * } else { - * aux(n - 1, s1 + s2, s1) + * aux(n - 1, s1 + s2, s1); * } * * // or: * // if (n == 0) ? s1 : aux(n - 1, s1 + s2, s1) * } * - * aux(n=n, s1=1, s2=0) + * aux(n=n, s1=1, s2=0); * } * - * def anotherfib = lambda(n : i32) { fib(n) } + * def x := "fu"; // non-constant + * x += "bar"; * - * def any : object - * def l : list = '() + * def anotherfib = lambda(n : i32) { fib(n) }; * - * deftype point :: {x : f64, y : f64} - * deftype polar :: {arg : f64, mag : f64} + * def any : object; + * def l : list = '(); + * + * deftype point :: {x : f64, y : f64}; + * deftype polar :: {arg : f64, mag : f64}; * * def polar2rect(pt : polar) -> point { * point(x = pt.mag * cos(arg), - * y = pt.mag * sin(arg)) + * y = pt.mag * sin(arg)); * } * * Grammar: - * toplevel-program = expression* + * toplevel-program = $expression(1); ..; $expression(n) + * * type-decl = decltype $typename [<$tp1 .. $tpn>] * expression = define-expr * | literal-expr @@ -245,6 +277,7 @@ namespace xo { * | apply-expr * | if-expr * | lambda-expr + * | arithmetic-expr * | block * * define-expr = type-decl @@ -297,6 +330,23 @@ namespace xo { * .., * $paramname(n) : $type(n)) body-expr * body-expr = expression + * + * arithmetic-expr = expression binop expression + * + * binop = + + * | - + * | * + * | / + * | | + * | & + * | ^ + * | == + * | != + * | < + * | <= + * | => + * | > + * **/ class parser { public: diff --git a/include/xo/reader/reader.hpp b/include/xo/reader/reader.hpp index 4009f722..28e0b3a6 100644 --- a/include/xo/reader/reader.hpp +++ b/include/xo/reader/reader.hpp @@ -18,6 +18,9 @@ namespace xo { using Expression = xo::ast::Expression; using span_type = span; + reader_result(rp expr, span_type rem) + : expr_{std::move(expr)}, rem_{rem} {} + /** parsed schematica expression **/ rp expr_; /** span giving text input consumed to construct expr, diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index b4a9eaf9..b5b7dcd6 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -21,7 +21,7 @@ namespace xo { namespace scm { const char * exprstatetype_descr(exprstatetype x) { - switch(x) { + switch (x) { case exprstatetype::invalid: return "?invalid"; case exprstatetype::expect_toplevel_expression_sequence: @@ -42,6 +42,8 @@ namespace xo { return "expect_symbol"; case exprstatetype::expect_type: return "expect_type"; + case exprstatetype::expr_progress: + return "expr_progress"; case exprstatetype::n_exprstatetype: break; } @@ -51,7 +53,7 @@ namespace xo { bool exprstate::admits_definition() const { - switch(exs_type_) { + switch (exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: return true; @@ -71,6 +73,8 @@ namespace xo { case exprstatetype::expect_symbol: case exprstatetype::expect_type: return false; + case exprstatetype::expr_progress: + return false; case exprstatetype::invalid: case exprstatetype::n_exprstatetype: /* unreachable */ @@ -82,7 +86,7 @@ namespace xo { bool exprstate::admits_symbol() const { - switch(exs_type_) { + switch (exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: case exprstatetype::def_0: case exprstatetype::def_1: @@ -102,6 +106,9 @@ namespace xo { /* treat symbol as typename */ return true; + case exprstatetype::expr_progress: + return false; + case exprstatetype::invalid: case exprstatetype::n_exprstatetype: /* unreachable */ @@ -113,7 +120,7 @@ namespace xo { bool exprstate::admits_colon() const { - switch(exs_type_) { + switch (exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: case exprstatetype::def_0: return false; @@ -132,6 +139,9 @@ namespace xo { case exprstatetype::expect_type: return false; + case exprstatetype::expr_progress: + return false; + case exprstatetype::invalid: case exprstatetype::n_exprstatetype: /* unreachable */ @@ -141,14 +151,37 @@ namespace xo { return false; } + bool + exprstate::admits_semicolon() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_symbol: + case exprstatetype::expect_type: + return false; + case exprstatetype::expr_progress: + return true; + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + return false; + } + + return false; + } + bool exprstate::admits_singleassign() const { - switch(exs_type_) { + switch (exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: /* - * def foo = 1 - * def foo : f64 = 1 + * def foo = 1 ; + * def foo : f64 = 1 ; * ^ ^ ^ ^ ^ ^ ^ * | | | | | | (done) * | | | | | def_4:expect_rhs_expression @@ -182,6 +215,9 @@ namespace xo { case exprstatetype::expect_type: return false; + case exprstatetype::expr_progress: + return false; + case exprstatetype::invalid: case exprstatetype::n_exprstatetype: /* unreachable */ @@ -193,7 +229,7 @@ namespace xo { bool exprstate::admits_f64() const { - switch(exs_type_) { + switch (exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: case exprstatetype::def_0: case exprstatetype::def_1: @@ -209,6 +245,9 @@ namespace xo { case exprstatetype::expect_type: return false; + case exprstatetype::expr_progress: + return false; + case exprstatetype::invalid: case exprstatetype::n_exprstatetype: /* unreachable */ @@ -218,6 +257,61 @@ namespace xo { return false; } +#ifdef NOT_YET + bool + exprstate::admits_leftparen() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + /* input like + * (function(blah...)) + * not allowed at toplevel; + * creates ambiguity e.g. consider + * x := foo + * (bar) + * + * is rhs 'foo' or 'foo(bar)' + */ + return false; + + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + /* input like + * def foo : f64 = ( + * ^ ^ ^ ^ ^ + * | | | | def_4 + * | | | def_3 + * | | def_2 + * | def_1 + * def_0 + * + * not allowed or relies on pushing another state + */ + return false; + + case exprstatetype::expect_rhs_expression: + /* can always begin non-toplevel expression with '(' */ + return true; + + case exprstatetype::expect_type: + return false; + + case exprstatetype::expect_symbol: + return false; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return false; + } + + return false; + } +#endif + void exprstate::on_def(exprstatestack * p_stack) { constexpr bool c_debug_flag = true; @@ -233,13 +327,13 @@ namespace xo { xtag("state", *this))); } - p_stack->push_exprstate(exprstate(exprstatetype::def_0, - DefineExprAccess::make_empty())); + p_stack->push_exprstate + (exprstate::def_0(DefineExprAccess::make_empty())); /* todo: replace: * expect_symbol_or_function_signature() */ - p_stack->push_exprstate(exprstatetype::expect_symbol); + p_stack->push_exprstate(exprstate::expect_symbol()); /* keyword 'def' introduces a definition: * def pi : f64 = 3.14159265 @@ -255,6 +349,8 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); + log && log(xtag("exstype", p_stack->top_exprstate().exs_type())); + constexpr const char * self_name = "exprstate::on_symbol"; if (!this->admits_symbol()) { @@ -265,7 +361,7 @@ namespace xo { xtag("state", *this))); } - switch(this->exs_type_) { + switch (this->exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: throw std::runtime_error (tostr(self_name, @@ -284,13 +380,44 @@ namespace xo { return; case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_symbol: - /* have to do pop first */ + { + /* various possibilities when looking for rhs expression: + * + * x := y // (1) + * x := f(a) // (2) + * x := f(a,b) // (3) + * + * need lookahead token following symbol to distinguish + * between (1) (symbol completes rhs expression) + * and {(2), (3)} (symbol is function call) + */ + /* have to do pop first, before sending symbol to + * the o.g. symbol-requester + */ +#ifdef NOT_YET + p_stack->push_exprstate(exprstate(exprstatetype::expr_progress, + Variable::make(name, type))); +#endif + +#ifdef LATER + p_stack->pop_exprstate(); + p_stack->top_exprstate().on_symbol(tk.text(), + p_stack, p_emit_expr); +#endif + return; + } + + case exprstatetype::expect_symbol: + { + /* have to do pop first, before sending symbol to + * the o.g. symbol-requester + */ p_stack->pop_exprstate(); p_stack->top_exprstate().on_symbol(tk.text(), p_stack, p_emit_expr); return; + } case exprstatetype::expect_type: { TypeDescr td = nullptr; @@ -321,20 +448,27 @@ namespace xo { return; } + case exprstatetype::expr_progress: + /* illegal input, e.g. + * foo bar + */ + assert(false); + return; + case exprstatetype::invalid: case exprstatetype::n_exprstatetype: /* unreachable */ assert(false); return; } - } + } /*on_symbol*/ void exprstate::on_typedescr(TypeDescr td, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { - switch(this->exs_type_) { + switch (this->exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: case exprstatetype::def_0: case exprstatetype::def_1: @@ -366,6 +500,10 @@ namespace xo { assert(false); return; + case exprstatetype::expr_progress: + assert(false); + return; + case exprstatetype::invalid: case exprstatetype::n_exprstatetype: /* unreachable */ @@ -392,7 +530,36 @@ namespace xo { if (this->exs_type_ == exprstatetype::def_1) { this->exs_type_ = exprstatetype::def_2; - p_stack->push_exprstate(exprstatetype::expect_type); + p_stack->push_exprstate(exprstate::expect_type()); + } else { + assert(false); + } + } + + void + exprstate::on_semicolon(exprstatestack * p_stack, + rp * p_emit_expr) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_semicolon"; + + if (!this->admits_semicolon()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected semicolon for parsing state", + xtag("state", *this))); + } + + if (this->exs_type_ == exprstatetype::expr_progress) { + rp expr = this->gen_expr_; + + p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + + p_stack->top_exprstate().on_expr(expr, + p_stack, + p_emit_expr); } else { assert(false); } @@ -417,16 +584,51 @@ namespace xo { { this->exs_type_ = exprstatetype::def_4; - p_stack->push_exprstate(exprstatetype::expect_rhs_expression); + p_stack->push_exprstate(exprstate::expect_rhs_expression()); } else { assert(false); } } +#ifdef OBSOLETE + /** + consider input: + + x := y(foo()) + + Is that an assignment x:=y followed by function call (foo()) ? + Or assignment with rhs calling a function y() with argument foo() + + policy: forbid parenthesis as beginning of a toplevel expression + **/ + + void + exprstate::on_leftparen(exprstatestack * p_stack, + rp * p_emit_expr) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + if (!this->admits_leftparen()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected leftparen '(' for parsing state", + xtag("state", *this))); + } + + if (this->exs_type_ == exprstatetype::expect_rhs_expression) { + /* push lparen_0 to remember to look for subsequent rightparen */ + p_stack->push_exprstate(exprstatetype::lparen_0); + + p_stack->push_exprstate(exprstatetype::expect_rhs_expression); + } + } +#endif + void exprstate::on_f64(const token_type & tk, exprstatestack * p_stack, - rp * p_emit_expr) + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -441,11 +643,9 @@ namespace xo { } if (this->exs_type_ == exprstatetype::expect_rhs_expression) { - p_stack->pop_exprstate(); - - p_stack->top_exprstate().on_expr(Constant::make(tk.f64_value()), - p_stack, - p_emit_expr); + p_stack->push_exprstate + (exprstate::make_expr_progress + (Constant::make(tk.f64_value()))); } else { assert(false); } @@ -461,7 +661,7 @@ namespace xo { log && log(xtag("tk", tk)); log && log(xtag("state", *this)); - switch(tk.tk_type()) { + switch (tk.tk_type()) { case tokentype::tk_def: this->on_def(p_stack); @@ -503,10 +703,13 @@ namespace xo { return; case tokentype::tk_doublecolon: - case tokentype::tk_semicolon: assert(false); return; + case tokentype::tk_semicolon: + this->on_semicolon(p_stack, p_emit_expr); + return; + case tokentype::tk_singleassign: this->on_singleassign(p_stack); return; @@ -538,7 +741,13 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr) { - switch(this->exs_type_) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("exstype", this->exs_type_), + xtag("expr", expr)); + + switch (this->exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: /* toplevel expression sequence accepts an * arbitrary number of expressions. @@ -546,7 +755,7 @@ namespace xo { * parser::include_token() returns */ - *p_emit_expr = expr.get(); + *p_emit_expr = expr.promote(); return; case exprstatetype::def_0: case exprstatetype::def_1: @@ -580,7 +789,17 @@ namespace xo { return; } - case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_rhs_expression: { + + p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + + p_stack->top_exprstate().on_expr(expr, + p_stack, + p_emit_expr); + + return; + } + case exprstatetype::expect_type: case exprstatetype::expect_symbol: /* unreachable @@ -588,13 +807,18 @@ namespace xo { */ assert(false); return; + case exprstatetype::expr_progress: + /* consecutive expressions isn't legal + */ + assert(false); + return; case exprstatetype::invalid: case exprstatetype::n_exprstatetype: /* unreachable */ assert(false); return; } - } + } /*on_expr*/ void exprstate::on_symbol(const std::string & symbol_name, @@ -634,6 +858,9 @@ namespace xo { */ assert(false); return; + case exprstatetype::expr_progress: + assert(false); + return; case exprstatetype::invalid: case exprstatetype::n_exprstatetype: /* unreachable */ @@ -645,9 +872,11 @@ namespace xo { void exprstate::print(std::ostream & os) const { os << ""; } @@ -667,6 +896,10 @@ namespace xo { void exprstatestack::push_exprstate(const exprstate & exs) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag), + xtag("exs", exs)); + std::size_t z = stack_.size(); stack_.resize(z+1); @@ -676,6 +909,10 @@ namespace xo { void exprstatestack::pop_exprstate() { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag), + xtag("top.exstype", top_exprstate().exs_type())); + std::size_t z = stack_.size(); if (z > 0) @@ -714,7 +951,7 @@ namespace xo { parser::include_token(const token_type & tk) { constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); + scope log(XO_DEBUG(c_debug_flag), xtag("tk", tk)); if (xs_stack_.empty()) { throw std::runtime_error(tostr("parser::include_token", @@ -729,6 +966,7 @@ namespace xo { xs_stack_.top_exprstate().on_input(tk, &xs_stack_, &retval); + log && log(xtag("retval", retval)); return retval; } /*include_token*/ diff --git a/src/reader/reader.cpp b/src/reader/reader.cpp index c24ebf94..d08b9552 100644 --- a/src/reader/reader.cpp +++ b/src/reader/reader.cpp @@ -17,6 +17,9 @@ namespace xo { reader_result reader::read_expr(const span_type & input_arg, bool eof) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + span_type input = input_arg; /* input text-span consumed by this call. @@ -32,7 +35,13 @@ namespace xo { const auto & tk = sr.first; const span_type & used_span = sr.second; + log && log(xtag("used_span", used_span)); + log && log(xtag("input.pre", input)); + input = input.after_prefix(used_span); + + log && log(xtag("expr_span.pre", expr_span)); + expr_span += used_span; if (tk.is_valid()) { @@ -40,6 +49,9 @@ namespace xo { auto expr = this->parser_.include_token(tk); if (expr) { + log && log(xtag("outcome", "victory!"), + xtag("expr", expr)); + /* token completes an expression -> victory */ return reader_result(expr, expr_span); } else { @@ -48,7 +60,6 @@ namespace xo { * * input span may contain more tokens -> iterate */ - input = input.after_prefix(used_span); } } else { assert(input.empty()); @@ -76,6 +87,8 @@ namespace xo { } } + log && log(xtag("outcome", "noop")); + return reader_result(nullptr, expr_span); } diff --git a/utest/parser.test.cpp b/utest/parser.test.cpp index aa230c7a..58910bda 100644 --- a/utest/parser.test.cpp +++ b/utest/parser.test.cpp @@ -201,7 +201,43 @@ namespace xo { cerr << "parser state after [def foo : f64 = 3.14159265]" << endl; cerr << parser << endl; - REQUIRE(r6.get() != nullptr); + REQUIRE(r6.get() == nullptr); + + /* stack should be + * + * expect_toplevel_expression_sequence + */ + CHECK(parser.stack_size() == 4); + if (parser.stack_size() > 0) + CHECK(parser.i_exstype(0) == exprstatetype::expr_progress); + if (parser.stack_size() > 1) + CHECK(parser.i_exstype(1) == exprstatetype::expect_rhs_expression); + if (parser.stack_size() > 2) + CHECK(parser.i_exstype(2) == exprstatetype::def_4); + if (parser.stack_size() > 3) + CHECK(parser.i_exstype(3) + == exprstatetype::expect_toplevel_expression_sequence); + } + + /* input: + * + * i_tc==0: + * def foo = 3.14159265 ; + * ^ ^ + * 0 1 + * + * i_tc==1: + * def foo : f64 = 3.14159265 ; + * ^ ^ + * 0 1 + */ + { + auto r7 = parser.include_token(token_type::semicolon()); + + cerr << "parser state after [def foo : f64 = 3.14159265;]" << endl; + cerr << parser << endl; + + REQUIRE(r7.get() != nullptr); CHECK(parser.stack_size() == 1); diff --git a/utest/reader.test.cpp b/utest/reader.test.cpp index 376dbf05..eff43622 100644 --- a/utest/reader.test.cpp +++ b/utest/reader.test.cpp @@ -18,11 +18,20 @@ namespace xo { rdr.begin_translation_unit(); try { - auto rr = rdr.read_expr(reader::span_type::from_cstr("def foo : f64 = 3.14159265"), - true /*eof*/); + auto input + = reader::span_type::from_cstr("def foo : f64 = 3.14159265;"); + auto rr + = rdr.read_expr(input, true /*eof*/); REQUIRE(rr.expr_.get()); - REQUIRE(rr.rem_.empty()); + + log && log(xtag("expr", rr.expr_)); + + input = input.after_prefix(rr.rem_); + + log && log(xtag("post.input", input)); + + REQUIRE(input.empty()); } catch (std::exception & ex) { log && log(ex.what()); From 6f42b348e259495bb7274fed4c1e95da99da3df1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 23:10:57 -0400 Subject: [PATCH 1317/2524] xo-tokenizer: tiny: addr in span printout --- include/xo/tokenizer/span.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/xo/tokenizer/span.hpp b/include/xo/tokenizer/span.hpp index e53bc18e..ca63759f 100644 --- a/include/xo/tokenizer/span.hpp +++ b/include/xo/tokenizer/span.hpp @@ -114,6 +114,7 @@ namespace xo { /** print representation for this span on stream @p os **/ void print(std::ostream & os) const { os << ""; From 3746f325297ff44d363cbfabe15a5efe0f3b972a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 6 Aug 2024 23:11:17 -0400 Subject: [PATCH 1318/2524] xo-tokenizer: fix: missing assignment --- include/xo/tokenizer/tokenizer.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/xo/tokenizer/tokenizer.hpp b/include/xo/tokenizer/tokenizer.hpp index 4fad37ba..09bb4d97 100644 --- a/include/xo/tokenizer/tokenizer.hpp +++ b/include/xo/tokenizer/tokenizer.hpp @@ -648,6 +648,10 @@ namespace xo { if (!sr.first.is_valid() && eof) { sr.first = this->notify_eof(); + /* always consume remainder of input here. + * ambiguous prefix can represent at most one token + */ + sr.second = input; } return sr; From 6ff2ac97b0f4764899ada6fe5416f7313eaaab72 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 7 Aug 2024 11:52:20 -0400 Subject: [PATCH 1319/2524] xo-reader: feat: handle parenthesized expressions --- include/xo/reader/parser.hpp | 41 +++++- src/reader/parser.cpp | 247 +++++++++++++++++++++++++++++++---- utest/reader.test.cpp | 25 +++- 3 files changed, 278 insertions(+), 35 deletions(-) diff --git a/include/xo/reader/parser.hpp b/include/xo/reader/parser.hpp index 2c26a8e9..715bc23f 100644 --- a/include/xo/reader/parser.hpp +++ b/include/xo/reader/parser.hpp @@ -25,6 +25,12 @@ namespace xo { def_2, def_3, def_4, + def_5, + + /* lparen_0: look for expression; capture + advance to lparen_1 */ + lparen_0, + /* lparen_1: expect rightparen */ + lparen_1, expect_rhs_expression, expect_symbol, @@ -46,6 +52,17 @@ namespace xo { class exprstatestack; +#ifdef NOT_YET + class exprstateaux { + public: + }; + + class lparen_xsa : public exprstateaux { + public: + private: + }; +#endif + /** state associated with a partially-parsed expression. **/ class exprstate { @@ -84,6 +101,9 @@ namespace xo { static exprstate def_0(rp def_expr) { return exprstate(exprstatetype::def_0, nullptr, def_expr); } + static exprstate lparen_0() { + return exprstate(exprstatetype::lparen_0, nullptr, nullptr); + } exprstatetype exs_type() const { return exs_type_; } @@ -99,10 +119,10 @@ namespace xo { bool admits_semicolon() const; /** true iff this parsing state admits a singleassign '=' as next token **/ bool admits_singleassign() const; -#ifdef NOT_YET /** true iff this parsing state admits a leftparen '(' as next token **/ bool admits_leftparen() const; -#endif + /** truee iff this parsing state admits a rightparen ')' as next token **/ + bool admits_rightparen() const; /** true iff this parsing state admits a 64-bit floating point literal token **/ bool admits_f64() const; @@ -134,10 +154,10 @@ namespace xo { void on_semicolon(exprstatestack * p_stack, rp * p_emit_expr); void on_singleassign(exprstatestack * p_stack); -#ifdef NOT_YET void on_leftparen(exprstatestack * p_stack, rp * p_emit_expr); -#endif + void on_rightparen(exprstatestack * p_stack, + rp * p_emit_expr); void on_f64(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); @@ -147,7 +167,7 @@ namespace xo { * def foo : f64 = 1 ; * ^ ^ ^ ^ ^ ^ ^ ^ * | | | | | | | (done) - * | | | | | | ?? + * | | | | | | def_4:expect_rhs_expression:expr_progress * | | | | | def_4:expect_rhs_expression * | | | | def_3 * | | | def_2:expect_type @@ -173,6 +193,11 @@ namespace xo { * May be nested within a def_expr **/ rp cvt_expr_; + +#ifdef NOT_YET + /* polymorphic state here */ + std::unique_ptr state_; +#endif }; /*exprstate*/ inline std::ostream & @@ -220,6 +245,12 @@ namespace xo { std::vector stack_; }; + inline std::ostream & + operator<< (std::ostream & os, const exprstatestack & x) { + x.print(os); + return os; + } + /** schematica parser * * Examples: diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index b5b7dcd6..a03199ad 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -36,6 +36,12 @@ namespace xo { return "def_3"; case exprstatetype::def_4: return "def_4"; + case exprstatetype::def_5: + return "def_5"; + case exprstatetype::lparen_0: + return "lparen_0"; + case exprstatetype::lparen_1: + return "lparen_1"; case exprstatetype::expect_rhs_expression: return "expect_rhs_expression"; case exprstatetype::expect_symbol: @@ -62,12 +68,15 @@ namespace xo { case exprstatetype::def_2: case exprstatetype::def_3: case exprstatetype::def_4: + case exprstatetype::def_5: /* note for def_4: * rhs could certainly be a function body that contains * nested defines; but then immediately-enclosing-exprstate * would be a block */ return false; + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: case exprstatetype::expect_rhs_expression: return false; case exprstatetype::expect_symbol: @@ -93,8 +102,11 @@ namespace xo { case exprstatetype::def_2: case exprstatetype::def_3: case exprstatetype::def_4: + case exprstatetype::def_5: return false; + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: case exprstatetype::expect_rhs_expression: /* treat symbol as variable name */ return true; @@ -131,6 +143,9 @@ namespace xo { case exprstatetype::def_2: case exprstatetype::def_3: case exprstatetype::def_4: + case exprstatetype::def_5: + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: case exprstatetype::expect_rhs_expression: /* rhs-expressions (or expressions for that matter) * may not begin with a colon @@ -160,6 +175,11 @@ namespace xo { case exprstatetype::def_2: case exprstatetype::def_3: case exprstatetype::def_4: + return false; + case exprstatetype::def_5: + return true; + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: case exprstatetype::expect_rhs_expression: case exprstatetype::expect_symbol: case exprstatetype::expect_type: @@ -206,7 +226,10 @@ namespace xo { return true; case exprstatetype::def_4: + case exprstatetype::def_5: + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: case exprstatetype::expect_rhs_expression: /* rhs-expressions (or expressions for that matter) * may not begin with singleassign '=' @@ -236,6 +259,13 @@ namespace xo { case exprstatetype::def_2: case exprstatetype::def_3: case exprstatetype::def_4: + case exprstatetype::def_5: + return false; + + case exprstatetype::lparen_0: + return true; + + case exprstatetype::lparen_1: return false; case exprstatetype::expect_rhs_expression: @@ -257,7 +287,6 @@ namespace xo { return false; } -#ifdef NOT_YET bool exprstate::admits_leftparen() const { switch (exs_type_) { @@ -278,6 +307,7 @@ namespace xo { case exprstatetype::def_2: case exprstatetype::def_3: case exprstatetype::def_4: + case exprstatetype::def_5: /* input like * def foo : f64 = ( * ^ ^ ^ ^ ^ @@ -291,6 +321,12 @@ namespace xo { */ return false; + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + /* unreachable */ + assert(false); + return false; + case exprstatetype::expect_rhs_expression: /* can always begin non-toplevel expression with '(' */ return true; @@ -301,6 +337,53 @@ namespace xo { case exprstatetype::expect_symbol: return false; + case exprstatetype::expr_progress: + /* todo: will parse as function call */ + return false; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return false; + } + + return false; + } + + bool + exprstate::admits_rightparen() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + return false; + + case exprstatetype::lparen_0: + /* unreachable -- will have pushed expect_rhs_expression */ + assert(false); + return false; + + case exprstatetype::lparen_1: + return true; + + case exprstatetype::expect_rhs_expression: + return false; + + case exprstatetype::expect_type: + return false; + + case exprstatetype::expect_symbol: + return false; + + case exprstatetype::expr_progress: + /* satisfies expression form */ + return true; + case exprstatetype::invalid: case exprstatetype::n_exprstatetype: /* unreachable */ @@ -310,7 +393,6 @@ namespace xo { return false; } -#endif void exprstate::on_def(exprstatestack * p_stack) { @@ -375,10 +457,22 @@ namespace xo { case exprstatetype::def_2: case exprstatetype::def_3: case exprstatetype::def_4: + case exprstatetype::def_5: /* unreachable */ assert(false); return; + case exprstatetype::lparen_0: + /* todo: variable reference */ + assert(false); + break; + + case exprstatetype::lparen_1: + /* unreachable */ + + assert(false); + break; + case exprstatetype::expect_rhs_expression: { /* various possibilities when looking for rhs expression: @@ -468,6 +562,8 @@ namespace xo { exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { + /* returning type description to somethign that wants it */ + switch (this->exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: case exprstatetype::def_0: @@ -487,10 +583,16 @@ namespace xo { case exprstatetype::def_3: case exprstatetype::def_4: + case exprstatetype::def_5: /* NOT IMPLEMENTED */ assert(false); return; + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + assert(false); + return; + case exprstatetype::expect_rhs_expression: case exprstatetype::expect_type: case exprstatetype::expect_symbol: @@ -557,6 +659,30 @@ namespace xo { p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + p_stack->top_exprstate().on_expr(expr, + p_stack, + p_emit_expr); + /* control here on input like: + * (1.234; + * + * a. '(' sets up stack [lparen_0:expect_rhs_expression] + * (see exprstate::on_leftparen()) + * b. 1.234 pushes (in case operators) [lparen_0:expect_rhs_expression:expr_progress] + * (see exprstate::on_f64()) + * c. semicolon completes expr_progress [lparen_0:expect_rhs_expression] + * deliver expresssion to expect_rhs_expression.on_expr() + * (see exprstate::on_expr()) + * d. expr_rhs_expression forwards expression to [lparen_0] + * e. lparen_0 advances to [lparen_1] + * f. now deliver semicolon; [lparen_1] rejects + */ + + p_stack->top_exprstate().on_semicolon(p_stack, p_emit_expr); + } else if (this->exs_type_ == exprstatetype::def_5) { + rp expr = this->def_expr_; + + p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); @@ -590,25 +716,15 @@ namespace xo { } } -#ifdef OBSOLETE - /** - consider input: - - x := y(foo()) - - Is that an assignment x:=y followed by function call (foo()) ? - Or assignment with rhs calling a function y() with argument foo() - - policy: forbid parenthesis as beginning of a toplevel expression - **/ - void exprstate::on_leftparen(exprstatestack * p_stack, - rp * p_emit_expr) + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); + constexpr const char * self_name = "exprstate::on_leftparen"; + if (!this->admits_leftparen()) { throw std::runtime_error(tostr(self_name, @@ -617,13 +733,64 @@ namespace xo { } if (this->exs_type_ == exprstatetype::expect_rhs_expression) { - /* push lparen_0 to remember to look for subsequent rightparen */ - p_stack->push_exprstate(exprstatetype::lparen_0); - - p_stack->push_exprstate(exprstatetype::expect_rhs_expression); + /* push lparen_0 to remember to look for subsequent rightparen. */ + p_stack->push_exprstate(exprstate::lparen_0()); + p_stack->push_exprstate(exprstate::expect_rhs_expression()); + } + } + + void + exprstate::on_rightparen(exprstatestack * p_stack, + rp * p_emit_expr) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_rightparen"; + + if (!this->admits_rightparen()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected rightparen ')' for parsing state", + xtag("state", *this))); + } + + if (this->exs_type_ == exprstatetype::expr_progress) { + /* stack may be something like: + * + * lparen_0 + * expect_rhs_expression + * expr_progress + * <-- rightparen + * + * 1. rightparen completes expression-in-progress + * 2. rightparen must then match innermost waiting lparen_0 + */ + + /* right paren confirms stack expression */ + rp expr = this->gen_expr_; + + p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + + if (p_stack->empty()) { + throw std::runtime_error(tostr(self_name, + ": expected non-empty parsing stack")); + } + + log && log(xtag("stack", *p_stack)); + + p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); + + /* now deliver rightparen */ + p_stack->top_exprstate().on_rightparen(p_stack, p_emit_expr); + } else if (this->exs_type_ == exprstatetype::lparen_1) { + rp expr = this->gen_expr_; + + p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + + p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); } } -#endif void exprstate::on_f64(const token_type & tk, @@ -643,6 +810,10 @@ namespace xo { } if (this->exs_type_ == exprstatetype::expect_rhs_expression) { + /* e.g. + * def pi = 3.14159265; + * \---tk---/ + */ p_stack->push_exprstate (exprstate::make_expr_progress (Constant::make(tk.f64_value()))); @@ -684,8 +855,13 @@ namespace xo { return; case tokentype::tk_leftparen: + this->on_leftparen(p_stack, p_emit_expr); + return; case tokentype::tk_rightparen: + this->on_rightparen(p_stack, p_emit_expr); + return; + case tokentype::tk_leftbracket: case tokentype::tk_rightbracket: case tokentype::tk_leftbrace: @@ -772,7 +948,7 @@ namespace xo { * Need to be able to locate variable by type * 2. if ir_type is an expression, adopt as rhs */ - rp rhs_value = expr.get(); + rp rhs_value = expr.promote(); if (this->cvt_expr_) this->cvt_expr_->assign_arg(rhs_value); @@ -781,11 +957,25 @@ namespace xo { rp def_expr = this->def_expr_; - p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + this->exs_type_ = exprstatetype::def_5; + return; + } - p_stack->top_exprstate().on_expr(def_expr, - p_stack, - p_emit_expr); + case exprstatetype::def_5: + assert(false); + return; + + case exprstatetype::lparen_0: { + this->exs_type_ = exprstatetype::lparen_1; /* wants on_rightparen */ + p_stack->push_exprstate(exprstate::make_expr_progress(expr.promote())); + + return; + } + + case exprstatetype::lparen_1: { + this->gen_expr_ = expr.promote(); + + /* expect immediate incoming call, this time to on_rightparen() */ return; } @@ -846,6 +1036,13 @@ namespace xo { case exprstatetype::def_2: case exprstatetype::def_3: case exprstatetype::def_4: + case exprstatetype::def_5: + /* NOT IMPLEMENTED */ + assert(false); + return; + + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: /* NOT IMPLEMENTED */ assert(false); return; diff --git a/utest/reader.test.cpp b/utest/reader.test.cpp index eff43622..8aff0011 100644 --- a/utest/reader.test.cpp +++ b/utest/reader.test.cpp @@ -7,19 +7,34 @@ namespace xo { using xo::scm::reader; namespace ut { + namespace { + struct test_case { + const char * text_; + }; + + std::vector s_testcase_v = { + {"def foo : f64 = 3.14159265;"}, + {"def foo : f64 = (3.14159265);"} + }; + } + TEST_CASE("reader", "[reader]") { - for (std::size_t i_tc = 0; i_tc < 1; ++i_tc) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag), xtag("utest", "reader")); + + for (std::size_t i_tc = 0; i_tc < s_testcase_v.size(); ++i_tc) { + const test_case & tc = s_testcase_v[i_tc]; + reader rdr; - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag), - xtag("utest", "reader"), xtag("i_tc", i_tc)); + scope log(XO_ENTER2(always, c_debug_flag, "reader.testcase"), + xtag("i_tc", i_tc)); rdr.begin_translation_unit(); try { auto input - = reader::span_type::from_cstr("def foo : f64 = 3.14159265;"); + = reader::span_type::from_cstr(tc.text_); auto rr = rdr.read_expr(input, true /*eof*/); From 6bc28cbfdf1bd6b208322fab8ff8f75dc7a578f9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 7 Aug 2024 16:01:05 -0400 Subject: [PATCH 1320/2524] xo-reader: refactor: exprstatestack holds ptrs to exprstates --- include/xo/reader/parser.hpp | 46 ++++++++++++++++++++---------------- src/reader/parser.cpp | 36 +++++++++++++++++----------- 2 files changed, 48 insertions(+), 34 deletions(-) diff --git a/include/xo/reader/parser.hpp b/include/xo/reader/parser.hpp index 715bc23f..ebdf2076 100644 --- a/include/xo/reader/parser.hpp +++ b/include/xo/reader/parser.hpp @@ -83,26 +83,26 @@ namespace xo { gen_expr_{std::move(candidate_expr)}, def_expr_{std::move(def_expr)} {} - static exprstate expect_toplevel_expression_sequence() { - return exprstate(exprstatetype::expect_toplevel_expression_sequence, nullptr, nullptr); + static std::unique_ptr expect_toplevel_expression_sequence() { + return std::make_unique(exprstate(exprstatetype::expect_toplevel_expression_sequence, nullptr, nullptr)); } - static exprstate expect_rhs_expression() { - return exprstate(exprstatetype::expect_rhs_expression, nullptr, nullptr); + static std::unique_ptr expect_rhs_expression() { + return std::make_unique(exprstate(exprstatetype::expect_rhs_expression, nullptr, nullptr)); } - static exprstate expect_symbol() { - return exprstate(exprstatetype::expect_symbol, nullptr, nullptr); + static std::unique_ptr expect_symbol() { + return std::make_unique(exprstate(exprstatetype::expect_symbol, nullptr, nullptr)); } - static exprstate expect_type() { - return exprstate(exprstatetype::expect_type, nullptr, nullptr); + static std::unique_ptr expect_type() { + return std::make_unique(exprstate(exprstatetype::expect_type, nullptr, nullptr)); } - static exprstate make_expr_progress(rp expr) { - return exprstate(exprstatetype::expr_progress, expr, nullptr); + static std::unique_ptr make_expr_progress(rp expr) { + return std::make_unique(exprstate(exprstatetype::expr_progress, expr, nullptr)); } - static exprstate def_0(rp def_expr) { - return exprstate(exprstatetype::def_0, nullptr, def_expr); + static std::unique_ptr def_0(rp def_expr) { + return std::make_unique(exprstate(exprstatetype::def_0, nullptr, def_expr)); } - static exprstate lparen_0() { - return exprstate(exprstatetype::lparen_0, nullptr, nullptr); + static std::unique_ptr lparen_0() { + return std::make_unique(exprstate(exprstatetype::lparen_0, nullptr, nullptr)); } exprstatetype exs_type() const { return exs_type_; } @@ -217,13 +217,13 @@ namespace xo { std::size_t size() const { return stack_.size(); } exprstate & top_exprstate(); - void push_exprstate(const exprstate & exs); - void pop_exprstate(); + void push_exprstate(std::unique_ptr exs); + std::unique_ptr pop_exprstate(); /** relative to top-of-stack. * 0 -> top (last in), z-1 -> bottom (first in) **/ - exprstate & operator[](std::size_t i) { + std::unique_ptr & operator[](std::size_t i) { std::size_t z = stack_.size(); assert(i < z); @@ -231,7 +231,7 @@ namespace xo { return stack_[z - i - 1]; } - const exprstate & operator[](std::size_t i) const { + const std::unique_ptr & operator[](std::size_t i) const { std::size_t z = stack_.size(); assert(i < z); @@ -242,7 +242,7 @@ namespace xo { void print (std::ostream & os) const; private: - std::vector stack_; + std::vector> stack_; }; inline std::ostream & @@ -251,6 +251,12 @@ namespace xo { return os; } + inline std::ostream & + operator<< (std::ostream & os, const exprstatestack * x) { + x->print(os); + return os; + } + /** schematica parser * * Examples: @@ -401,7 +407,7 @@ namespace xo { std::size_t z = xs_stack_.size(); if (i < z) { - return xs_stack_[i].exs_type(); + return xs_stack_[i]->exs_type(); } /* out of bounds */ diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index a03199ad..6cef967b 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -507,7 +507,8 @@ namespace xo { /* have to do pop first, before sending symbol to * the o.g. symbol-requester */ - p_stack->pop_exprstate(); + std::unique_ptr self = p_stack->pop_exprstate(); + p_stack->top_exprstate().on_symbol(tk.text(), p_stack, p_emit_expr); return; @@ -537,7 +538,7 @@ namespace xo { xtag("typename", tk.text()))); } - p_stack->pop_exprstate(); + std::unique_ptr self = p_stack->pop_exprstate(); p_stack->top_exprstate().on_typedescr(td, p_stack, p_emit_expr); return; } @@ -657,7 +658,7 @@ namespace xo { if (this->exs_type_ == exprstatetype::expr_progress) { rp expr = this->gen_expr_; - p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ p_stack->top_exprstate().on_expr(expr, p_stack, @@ -681,7 +682,7 @@ namespace xo { } else if (this->exs_type_ == exprstatetype::def_5) { rp expr = this->def_expr_; - p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ p_stack->top_exprstate().on_expr(expr, p_stack, @@ -770,14 +771,14 @@ namespace xo { /* right paren confirms stack expression */ rp expr = this->gen_expr_; - p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ if (p_stack->empty()) { throw std::runtime_error(tostr(self_name, ": expected non-empty parsing stack")); } - log && log(xtag("stack", *p_stack)); + log && log(xtag("stack", p_stack)); p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); @@ -786,7 +787,7 @@ namespace xo { } else if (this->exs_type_ == exprstatetype::lparen_1) { rp expr = this->gen_expr_; - p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); } @@ -981,7 +982,7 @@ namespace xo { case exprstatetype::expect_rhs_expression: { - p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ p_stack->top_exprstate().on_expr(expr, p_stack, @@ -1088,23 +1089,23 @@ namespace xo { ("parser::top_exprstate: unexpected empty stack"); } - return stack_[z-1]; + return *(stack_[z-1]); } void - exprstatestack::push_exprstate(const exprstate & exs) { + exprstatestack::push_exprstate(std::unique_ptr exs) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag), - xtag("exs", exs)); + xtag("exs", *exs)); std::size_t z = stack_.size(); stack_.resize(z+1); - stack_[z] = exs; + stack_[z] = std::move(exs); } - void + std::unique_ptr exprstatestack::pop_exprstate() { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag), @@ -1112,8 +1113,15 @@ namespace xo { std::size_t z = stack_.size(); - if (z > 0) + if (z > 0) { + std::unique_ptr top = std::move(stack_[z-1]); + stack_.resize(z-1); + + return top; + } else { + return nullptr; + } } void From e58f1ac62df933bb5be50b51c04a7a1ef19fc9bc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 7 Aug 2024 16:05:21 -0400 Subject: [PATCH 1321/2524] xo-reader: prep: virtual-ize exprstate methods --- include/xo/reader/parser.hpp | 76 +++++++++++++++++------------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/include/xo/reader/parser.hpp b/include/xo/reader/parser.hpp index ebdf2076..587b1173 100644 --- a/include/xo/reader/parser.hpp +++ b/include/xo/reader/parser.hpp @@ -82,6 +82,7 @@ namespace xo { : exs_type_{exs_type}, gen_expr_{std::move(candidate_expr)}, def_expr_{std::move(def_expr)} {} + virtual ~exprstate() = default; static std::unique_ptr expect_toplevel_expression_sequence() { return std::make_unique(exprstate(exprstatetype::expect_toplevel_expression_sequence, nullptr, nullptr)); @@ -110,59 +111,59 @@ namespace xo { /** true iff this parsing state admits a 'def' keyword * as next token **/ - bool admits_definition() const; + virtual bool admits_definition() const; /** true iff this parsing state admits a symbol as next token **/ - bool admits_symbol() const; + virtual bool admits_symbol() const; /** true iff this parsing state admits a colon as next token **/ - bool admits_colon() const; + virtual bool admits_colon() const; /** true iff this parsing state admits a semicolon as next token **/ - bool admits_semicolon() const; + virtual bool admits_semicolon() const; /** true iff this parsing state admits a singleassign '=' as next token **/ - bool admits_singleassign() const; + virtual bool admits_singleassign() const; /** true iff this parsing state admits a leftparen '(' as next token **/ - bool admits_leftparen() const; + virtual bool admits_leftparen() const; /** truee iff this parsing state admits a rightparen ')' as next token **/ - bool admits_rightparen() const; + virtual bool admits_rightparen() const; /** true iff this parsing state admits a 64-bit floating point literal token **/ - bool admits_f64() const; + virtual bool admits_f64() const; /** update exprstate in response to incoming token @p tk, * forward instructions to parent parser **/ void on_input(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); /** update exprstate in response to a successfully-parsed subexpression **/ - void on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * p_emit_expr); + virtual void on_expr(ref::brw expr, + exprstatestack * p_stack, + rp * p_emit_expr); /** update exprstate when expecting a symbol **/ - void on_symbol(const std::string & symbol, - exprstatestack * p_stack, - rp * p_emit_expr); + virtual void on_symbol(const std::string & symbol, + exprstatestack * p_stack, + rp * p_emit_expr); /** update exprstate when expeccting a typedescr **/ - void on_typedescr(TypeDescr td, - exprstatestack * p_stack, - rp * p_emit_expr); + virtual void on_typedescr(TypeDescr td, + exprstatestack * p_stack, + rp * p_emit_expr); /** print human-readable representation on @p os **/ - void print(std::ostream & os) const; + virtual void print(std::ostream & os) const; - private: - void on_def(exprstatestack * p_stack); - void on_symbol(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); - void on_colon(exprstatestack * p_stack); - void on_semicolon(exprstatestack * p_stack, - rp * p_emit_expr); - void on_singleassign(exprstatestack * p_stack); - void on_leftparen(exprstatestack * p_stack, - rp * p_emit_expr); - void on_rightparen(exprstatestack * p_stack, - rp * p_emit_expr); - void on_f64(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); + protected: + virtual void on_def(exprstatestack * p_stack); + virtual void on_symbol(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); + virtual void on_colon(exprstatestack * p_stack); + virtual void on_semicolon(exprstatestack * p_stack, + rp * p_emit_expr); + virtual void on_singleassign(exprstatestack * p_stack); + virtual void on_leftparen(exprstatestack * p_stack, + rp * p_emit_expr); + virtual void on_rightparen(exprstatestack * p_stack, + rp * p_emit_expr); + virtual void on_f64(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); - private: + protected: /** * def foo : f64 = 1 ; * ^ ^ ^ ^ ^ ^ ^ ^ @@ -193,11 +194,6 @@ namespace xo { * May be nested within a def_expr **/ rp cvt_expr_; - -#ifdef NOT_YET - /* polymorphic state here */ - std::unique_ptr state_; -#endif }; /*exprstate*/ inline std::ostream & From 739d8efe8276d1768196b24dd59e447dd321542b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 7 Aug 2024 16:08:03 -0400 Subject: [PATCH 1322/2524] xo-reader: reorg: exprstate to own .hpp file --- include/xo/reader/exprstate.hpp | 259 ++++++++++++++++++++++++++++++++ include/xo/reader/parser.hpp | 245 +----------------------------- 2 files changed, 260 insertions(+), 244 deletions(-) create mode 100644 include/xo/reader/exprstate.hpp diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp new file mode 100644 index 00000000..0a59c233 --- /dev/null +++ b/include/xo/reader/exprstate.hpp @@ -0,0 +1,259 @@ +/** @file exprstate.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/expression/Expression.hpp" +#include "xo/expression/DefineExpr.hpp" +#include "xo/expression/ConvertExpr.hpp" +#include "xo/tokenizer/token.hpp" +#include +//#include + +namespace xo { + namespace scm { + enum class exprstatetype { + invalid = -1, + + /** toplevel of some translation unit **/ + expect_toplevel_expression_sequence, + + def_0, + def_1, + def_2, + def_3, + def_4, + def_5, + + /* lparen_0: look for expression; capture + advance to lparen_1 */ + lparen_0, + /* lparen_1: expect rightparen */ + lparen_1, + + expect_rhs_expression, + expect_symbol, + expect_type, + + expr_progress, + + n_exprstatetype + }; + + extern const char * + exprstatetype_descr(exprstatetype x); + + inline std::ostream & + operator<< (std::ostream & os, exprstatetype x) { + os << exprstatetype_descr(x); + return os; + } + + class exprstatestack; + +#ifdef NOT_YET + class exprstateaux { + public: + }; + + class lparen_xsa : public exprstateaux { + public: + private: + }; +#endif + + /** state associated with a partially-parsed expression. + **/ + class exprstate { + public: + using Expression = xo::ast::Expression; + using DefineExprAccess = xo::ast::DefineExprAccess; + using ConvertExprAccess = xo::ast::ConvertExprAccess; + using exprtype = xo::ast::exprtype; + using token_type = token; + using TypeDescr = xo::reflect::TypeDescr; + + public: + exprstate() = default; + exprstate(exprstatetype exs_type, + rp candidate_expr, + rp def_expr) + : exs_type_{exs_type}, + gen_expr_{std::move(candidate_expr)}, + def_expr_{std::move(def_expr)} {} + virtual ~exprstate() = default; + + static std::unique_ptr expect_toplevel_expression_sequence() { + return std::make_unique(exprstate(exprstatetype::expect_toplevel_expression_sequence, nullptr, nullptr)); + } + static std::unique_ptr expect_rhs_expression() { + return std::make_unique(exprstate(exprstatetype::expect_rhs_expression, nullptr, nullptr)); + } + static std::unique_ptr expect_symbol() { + return std::make_unique(exprstate(exprstatetype::expect_symbol, nullptr, nullptr)); + } + static std::unique_ptr expect_type() { + return std::make_unique(exprstate(exprstatetype::expect_type, nullptr, nullptr)); + } + static std::unique_ptr make_expr_progress(rp expr) { + return std::make_unique(exprstate(exprstatetype::expr_progress, expr, nullptr)); + } + static std::unique_ptr def_0(rp def_expr) { + return std::make_unique(exprstate(exprstatetype::def_0, nullptr, def_expr)); + } + static std::unique_ptr lparen_0() { + return std::make_unique(exprstate(exprstatetype::lparen_0, nullptr, nullptr)); + } + + exprstatetype exs_type() const { return exs_type_; } + + /** true iff this parsing state admits a 'def' keyword + * as next token + **/ + virtual bool admits_definition() const; + /** true iff this parsing state admits a symbol as next token **/ + virtual bool admits_symbol() const; + /** true iff this parsing state admits a colon as next token **/ + virtual bool admits_colon() const; + /** true iff this parsing state admits a semicolon as next token **/ + virtual bool admits_semicolon() const; + /** true iff this parsing state admits a singleassign '=' as next token **/ + virtual bool admits_singleassign() const; + /** true iff this parsing state admits a leftparen '(' as next token **/ + virtual bool admits_leftparen() const; + /** truee iff this parsing state admits a rightparen ')' as next token **/ + virtual bool admits_rightparen() const; + /** true iff this parsing state admits a 64-bit floating point literal token **/ + virtual bool admits_f64() const; + + /** update exprstate in response to incoming token @p tk, + * forward instructions to parent parser + **/ + void on_input(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); + /** update exprstate in response to a successfully-parsed subexpression **/ + virtual void on_expr(ref::brw expr, + exprstatestack * p_stack, + rp * p_emit_expr); + /** update exprstate when expecting a symbol **/ + virtual void on_symbol(const std::string & symbol, + exprstatestack * p_stack, + rp * p_emit_expr); + /** update exprstate when expeccting a typedescr **/ + virtual void on_typedescr(TypeDescr td, + exprstatestack * p_stack, + rp * p_emit_expr); + /** print human-readable representation on @p os **/ + virtual void print(std::ostream & os) const; + + protected: + virtual void on_def(exprstatestack * p_stack); + virtual void on_symbol(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); + virtual void on_colon(exprstatestack * p_stack); + virtual void on_semicolon(exprstatestack * p_stack, + rp * p_emit_expr); + virtual void on_singleassign(exprstatestack * p_stack); + virtual void on_leftparen(exprstatestack * p_stack, + rp * p_emit_expr); + virtual void on_rightparen(exprstatestack * p_stack, + rp * p_emit_expr); + virtual void on_f64(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); + + protected: + /** + * def foo : f64 = 1 ; + * ^ ^ ^ ^ ^ ^ ^ ^ + * | | | | | | | (done) + * | | | | | | def_4:expect_rhs_expression:expr_progress + * | | | | | def_4:expect_rhs_expression + * | | | | def_3 + * | | | def_2:expect_type + * | | def_1 + * | def_0:expect_symbol + * expect_toplevel_expression_sequence + * + * def_0:expect_symbol: got 'def' keyword, symbol to follow + * def_1: got symbol name + * def_2:expect_symbol got (optional) colon, type name to follow + * def_3: got symbol type + * def_4:expect_rhs_expression got (optional) equal sign, value to follow + * (done): definition complete, pop exprstate from stack + * + **/ + exprstatetype exs_type_; + + /** generic expression **/ + rp gen_expr_; + /** scaffold a define-expression here **/ + rp def_expr_; + /** scafford a convert-expression here. + * May be nested within a def_expr + **/ + rp cvt_expr_; + }; /*exprstate*/ + + inline std::ostream & + operator<< (std::ostream & os, const exprstate & x) { + x.print(os); + return os; + } + + /** @class exprstatestack + * @brief A stack of exprstate objects + **/ + class exprstatestack { + public: + exprstatestack() {} + + bool empty() const { return stack_.empty(); } + std::size_t size() const { return stack_.size(); } + + exprstate & top_exprstate(); + void push_exprstate(std::unique_ptr exs); + std::unique_ptr pop_exprstate(); + + /** relative to top-of-stack. + * 0 -> top (last in), z-1 -> bottom (first in) + **/ + std::unique_ptr & operator[](std::size_t i) { + std::size_t z = stack_.size(); + + assert(i < z); + + return stack_[z - i - 1]; + } + + const std::unique_ptr & operator[](std::size_t i) const { + std::size_t z = stack_.size(); + + assert(i < z); + + return stack_[z - i - 1]; + } + + void print (std::ostream & os) const; + + private: + std::vector> stack_; + }; + + inline std::ostream & + operator<< (std::ostream & os, const exprstatestack & x) { + x.print(os); + return os; + } + + inline std::ostream & + operator<< (std::ostream & os, const exprstatestack * x) { + x->print(os); + return os; + } + } /*namespace scm*/ +} /*namespace xo*/ + + +/** end exprstate.hpp **/ diff --git a/include/xo/reader/parser.hpp b/include/xo/reader/parser.hpp index 587b1173..fb760081 100644 --- a/include/xo/reader/parser.hpp +++ b/include/xo/reader/parser.hpp @@ -5,254 +5,11 @@ #pragma once -#include "xo/expression/Expression.hpp" -#include "xo/expression/DefineExpr.hpp" -#include "xo/expression/ConvertExpr.hpp" -#include "xo/tokenizer/token.hpp" -#include +#include "exprstate.hpp" #include namespace xo { namespace scm { - enum class exprstatetype { - invalid = -1, - - /** toplevel of some translation unit **/ - expect_toplevel_expression_sequence, - - def_0, - def_1, - def_2, - def_3, - def_4, - def_5, - - /* lparen_0: look for expression; capture + advance to lparen_1 */ - lparen_0, - /* lparen_1: expect rightparen */ - lparen_1, - - expect_rhs_expression, - expect_symbol, - expect_type, - - expr_progress, - - n_exprstatetype - }; - - extern const char * - exprstatetype_descr(exprstatetype x); - - inline std::ostream & - operator<< (std::ostream & os, exprstatetype x) { - os << exprstatetype_descr(x); - return os; - } - - class exprstatestack; - -#ifdef NOT_YET - class exprstateaux { - public: - }; - - class lparen_xsa : public exprstateaux { - public: - private: - }; -#endif - - /** state associated with a partially-parsed expression. - **/ - class exprstate { - public: - using Expression = xo::ast::Expression; - using DefineExprAccess = xo::ast::DefineExprAccess; - using ConvertExprAccess = xo::ast::ConvertExprAccess; - using exprtype = xo::ast::exprtype; - using token_type = token; - using TypeDescr = xo::reflect::TypeDescr; - - public: - exprstate() = default; - exprstate(exprstatetype exs_type, - rp candidate_expr, - rp def_expr) - : exs_type_{exs_type}, - gen_expr_{std::move(candidate_expr)}, - def_expr_{std::move(def_expr)} {} - virtual ~exprstate() = default; - - static std::unique_ptr expect_toplevel_expression_sequence() { - return std::make_unique(exprstate(exprstatetype::expect_toplevel_expression_sequence, nullptr, nullptr)); - } - static std::unique_ptr expect_rhs_expression() { - return std::make_unique(exprstate(exprstatetype::expect_rhs_expression, nullptr, nullptr)); - } - static std::unique_ptr expect_symbol() { - return std::make_unique(exprstate(exprstatetype::expect_symbol, nullptr, nullptr)); - } - static std::unique_ptr expect_type() { - return std::make_unique(exprstate(exprstatetype::expect_type, nullptr, nullptr)); - } - static std::unique_ptr make_expr_progress(rp expr) { - return std::make_unique(exprstate(exprstatetype::expr_progress, expr, nullptr)); - } - static std::unique_ptr def_0(rp def_expr) { - return std::make_unique(exprstate(exprstatetype::def_0, nullptr, def_expr)); - } - static std::unique_ptr lparen_0() { - return std::make_unique(exprstate(exprstatetype::lparen_0, nullptr, nullptr)); - } - - exprstatetype exs_type() const { return exs_type_; } - - /** true iff this parsing state admits a 'def' keyword - * as next token - **/ - virtual bool admits_definition() const; - /** true iff this parsing state admits a symbol as next token **/ - virtual bool admits_symbol() const; - /** true iff this parsing state admits a colon as next token **/ - virtual bool admits_colon() const; - /** true iff this parsing state admits a semicolon as next token **/ - virtual bool admits_semicolon() const; - /** true iff this parsing state admits a singleassign '=' as next token **/ - virtual bool admits_singleassign() const; - /** true iff this parsing state admits a leftparen '(' as next token **/ - virtual bool admits_leftparen() const; - /** truee iff this parsing state admits a rightparen ')' as next token **/ - virtual bool admits_rightparen() const; - /** true iff this parsing state admits a 64-bit floating point literal token **/ - virtual bool admits_f64() const; - - /** update exprstate in response to incoming token @p tk, - * forward instructions to parent parser - **/ - void on_input(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); - /** update exprstate in response to a successfully-parsed subexpression **/ - virtual void on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * p_emit_expr); - /** update exprstate when expecting a symbol **/ - virtual void on_symbol(const std::string & symbol, - exprstatestack * p_stack, - rp * p_emit_expr); - /** update exprstate when expeccting a typedescr **/ - virtual void on_typedescr(TypeDescr td, - exprstatestack * p_stack, - rp * p_emit_expr); - /** print human-readable representation on @p os **/ - virtual void print(std::ostream & os) const; - - protected: - virtual void on_def(exprstatestack * p_stack); - virtual void on_symbol(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); - virtual void on_colon(exprstatestack * p_stack); - virtual void on_semicolon(exprstatestack * p_stack, - rp * p_emit_expr); - virtual void on_singleassign(exprstatestack * p_stack); - virtual void on_leftparen(exprstatestack * p_stack, - rp * p_emit_expr); - virtual void on_rightparen(exprstatestack * p_stack, - rp * p_emit_expr); - virtual void on_f64(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); - - protected: - /** - * def foo : f64 = 1 ; - * ^ ^ ^ ^ ^ ^ ^ ^ - * | | | | | | | (done) - * | | | | | | def_4:expect_rhs_expression:expr_progress - * | | | | | def_4:expect_rhs_expression - * | | | | def_3 - * | | | def_2:expect_type - * | | def_1 - * | def_0:expect_symbol - * expect_toplevel_expression_sequence - * - * def_0:expect_symbol: got 'def' keyword, symbol to follow - * def_1: got symbol name - * def_2:expect_symbol got (optional) colon, type name to follow - * def_3: got symbol type - * def_4:expect_rhs_expression got (optional) equal sign, value to follow - * (done): definition complete, pop exprstate from stack - * - **/ - exprstatetype exs_type_; - - /** generic expression **/ - rp gen_expr_; - /** scaffold a define-expression here **/ - rp def_expr_; - /** scafford a convert-expression here. - * May be nested within a def_expr - **/ - rp cvt_expr_; - }; /*exprstate*/ - - inline std::ostream & - operator<< (std::ostream & os, const exprstate & x) { - x.print(os); - return os; - } - - /** @class exprstatestack - * @brief A stack of exprstate objects - **/ - class exprstatestack { - public: - exprstatestack() {} - - bool empty() const { return stack_.empty(); } - std::size_t size() const { return stack_.size(); } - - exprstate & top_exprstate(); - void push_exprstate(std::unique_ptr exs); - std::unique_ptr pop_exprstate(); - - /** relative to top-of-stack. - * 0 -> top (last in), z-1 -> bottom (first in) - **/ - std::unique_ptr & operator[](std::size_t i) { - std::size_t z = stack_.size(); - - assert(i < z); - - return stack_[z - i - 1]; - } - - const std::unique_ptr & operator[](std::size_t i) const { - std::size_t z = stack_.size(); - - assert(i < z); - - return stack_[z - i - 1]; - } - - void print (std::ostream & os) const; - - private: - std::vector> stack_; - }; - - inline std::ostream & - operator<< (std::ostream & os, const exprstatestack & x) { - x.print(os); - return os; - } - - inline std::ostream & - operator<< (std::ostream & os, const exprstatestack * x) { - x->print(os); - return os; - } - /** schematica parser * * Examples: From c4b58bb29889c23aeea2aaee5cb157bfafdebdf7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 7 Aug 2024 16:28:39 -0400 Subject: [PATCH 1323/2524] xo-reader: refactor: move def on_expr() to define_xs subtype --- include/xo/reader/define_xs.hpp | 52 ++++++++++++++++++++++++ include/xo/reader/exprstate.hpp | 24 ----------- src/reader/CMakeLists.txt | 4 +- src/reader/define_xs.cpp | 71 +++++++++++++++++++++++++++++++++ src/reader/exprstate.cpp | 15 +++++++ src/reader/parser.cpp | 29 ++------------ 6 files changed, 145 insertions(+), 50 deletions(-) create mode 100644 include/xo/reader/define_xs.hpp create mode 100644 src/reader/define_xs.cpp create mode 100644 src/reader/exprstate.cpp diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp new file mode 100644 index 00000000..43cd445c --- /dev/null +++ b/include/xo/reader/define_xs.hpp @@ -0,0 +1,52 @@ +/** @file define_xs.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "exprstate.hpp" +//#include + +namespace xo { + namespace scm { + /** @class define_xs + * @brief state to provide parsing of a define-expression + **/ + class define_xs : public exprstate { + public: + define_xs(rp def_expr); + virtual ~define_xs() = default; + + static std::unique_ptr def_0(rp def_expr); + + virtual void on_expr(ref::brw expr, + exprstatestack * p_stack, + rp * p_emit_expr) override; + private: + /** + * def foo : f64 = 1 ; + * ^ ^ ^ ^ ^ ^ ^ ^ + * | | | | | | | (done) + * | | | | | | def_4:expect_rhs_expression:expr_progress + * | | | | | def_4:expect_rhs_expression + * | | | | def_3 + * | | | def_2:expect_type + * | | def_1 + * | def_0:expect_symbol + * expect_toplevel_expression_sequence + * + * def_0:expect_symbol: got 'def' keyword, symbol to follow + * def_1: got symbol name + * def_2:expect_symbol got (optional) colon, type name to follow + * def_3: got symbol type + * def_4:expect_rhs_expression got (optional) equal sign, value to follow + * (done): definition complete, pop exprstate from stack + * + **/ + }; + } /*namespace scm*/ +} /*namespace xo*/ + + +/** end define_xs.hpp **/ diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 0a59c233..461ecddf 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -99,9 +99,6 @@ namespace xo { static std::unique_ptr make_expr_progress(rp expr) { return std::make_unique(exprstate(exprstatetype::expr_progress, expr, nullptr)); } - static std::unique_ptr def_0(rp def_expr) { - return std::make_unique(exprstate(exprstatetype::def_0, nullptr, def_expr)); - } static std::unique_ptr lparen_0() { return std::make_unique(exprstate(exprstatetype::lparen_0, nullptr, nullptr)); } @@ -164,26 +161,6 @@ namespace xo { rp * p_emit_expr); protected: - /** - * def foo : f64 = 1 ; - * ^ ^ ^ ^ ^ ^ ^ ^ - * | | | | | | | (done) - * | | | | | | def_4:expect_rhs_expression:expr_progress - * | | | | | def_4:expect_rhs_expression - * | | | | def_3 - * | | | def_2:expect_type - * | | def_1 - * | def_0:expect_symbol - * expect_toplevel_expression_sequence - * - * def_0:expect_symbol: got 'def' keyword, symbol to follow - * def_1: got symbol name - * def_2:expect_symbol got (optional) colon, type name to follow - * def_3: got symbol type - * def_4:expect_rhs_expression got (optional) equal sign, value to follow - * (done): definition complete, pop exprstate from stack - * - **/ exprstatetype exs_type_; /** generic expression **/ @@ -255,5 +232,4 @@ namespace xo { } /*namespace scm*/ } /*namespace xo*/ - /** end exprstate.hpp **/ diff --git a/src/reader/CMakeLists.txt b/src/reader/CMakeLists.txt index 687ae4e1..8896040e 100644 --- a/src/reader/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -3,7 +3,9 @@ set(SELF_LIB xo_reader) set(SELF_SRCS parser.cpp - reader.cpp) + reader.cpp + exprstate.cpp + define_xs.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_dependency(${SELF_LIB} xo_expression) diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp new file mode 100644 index 00000000..57526378 --- /dev/null +++ b/src/reader/define_xs.cpp @@ -0,0 +1,71 @@ +/* @file define_xs.cpp */ + +#include "define_xs.hpp" + +namespace xo { + namespace scm { + define_xs::define_xs(rp def_expr) + : exprstate(exprstatetype::def_0, + nullptr /*gen_expr*/, + def_expr) + {} + + void + define_xs::on_expr(ref::brw expr, + exprstatestack * /* p_stack */, + rp * /* p_emit_expr */) + { + switch (this->exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + assert(false); + return; + + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + /* NOT IMPLEMENTED */ + assert(false); + return; + case exprstatetype::def_4: { + /* have all the ingredients to create an expression + * representing a definition + * + * 1. if ir_type is a symbol, interpret as variable name. + * Need to be able to locate variable by type + * 2. if ir_type is an expression, adopt as rhs + */ + rp rhs_value = expr.promote(); + + if (this->cvt_expr_) + this->cvt_expr_->assign_arg(rhs_value); + else + this->def_expr_->assign_rhs(rhs_value);; + + rp def_expr = this->def_expr_; + + this->exs_type_ = exprstatetype::def_5; + return; + } + + case exprstatetype::def_5: + assert(false); + return; + + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_type: + case exprstatetype::expect_symbol: + case exprstatetype::expr_progress: + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return; + } + } + } /*namespace scm*/ +} /*namespace xo*/ + +/* end define_xs.cpp */ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp new file mode 100644 index 00000000..c6b2ef36 --- /dev/null +++ b/src/reader/exprstate.cpp @@ -0,0 +1,15 @@ +/* @file exprstate.cpp */ + +#include "exprstate.hpp" +#include "define_xs.hpp" + +namespace xo { + namespace scm { + std::unique_ptr + define_xs::def_0(rp def_expr) { + return std::make_unique(define_xs(def_expr)); + } + } /*namespace scm*/ +} /*namespace xo*/ + +/* end exprstate.cpp */ diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index 6cef967b..63d19f93 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -4,6 +4,7 @@ */ #include "parser.hpp" +#include "define_xs.hpp" #include "xo/expression/DefineExpr.hpp" #include "xo/expression/Constant.hpp" #include "xo/expression/ConvertExpr.hpp" @@ -410,7 +411,7 @@ namespace xo { } p_stack->push_exprstate - (exprstate::def_0(DefineExprAccess::make_empty())); + (define_xs::def_0(DefineExprAccess::make_empty())); /* todo: replace: * expect_symbol_or_function_signature() @@ -938,31 +939,9 @@ namespace xo { case exprstatetype::def_1: case exprstatetype::def_2: case exprstatetype::def_3: - /* NOT IMPLEMENTED */ - assert(false); - return; - case exprstatetype::def_4: { - /* have all the ingredients to create an expression - * representing a definition - * - * 1. if ir_type is a symbol, interpret as variable name. - * Need to be able to locate variable by type - * 2. if ir_type is an expression, adopt as rhs - */ - rp rhs_value = expr.promote(); - - if (this->cvt_expr_) - this->cvt_expr_->assign_arg(rhs_value); - else - this->def_expr_->assign_rhs(rhs_value);; - - rp def_expr = this->def_expr_; - - this->exs_type_ = exprstatetype::def_5; - return; - } - + case exprstatetype::def_4: case exprstatetype::def_5: + /* unreachable. see define_xs::on_expr() */ assert(false); return; From 3a1db8ca022c109af74fe5754675ead482f3dcd7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 7 Aug 2024 16:32:32 -0400 Subject: [PATCH 1324/2524] xo-reader: refactor: move defexpr on_symbol to dedicated define_xs --- include/xo/reader/define_xs.hpp | 4 ++++ src/reader/define_xs.cpp | 39 +++++++++++++++++++++++++++++++++ src/reader/parser.cpp | 9 ++------ 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 43cd445c..c872a58b 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -23,6 +23,10 @@ namespace xo { virtual void on_expr(ref::brw expr, exprstatestack * p_stack, rp * p_emit_expr) override; + virtual void on_symbol(const std::string & symbol_name, + exprstatestack * p_stack, + rp * p_emit_expr) override; + private: /** * def foo : f64 = 1 ; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 57526378..09148c9e 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -65,6 +65,45 @@ namespace xo { return; } } + + void + define_xs::on_symbol(const std::string & symbol_name, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + switch (this->exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + /* unreachable */ + assert(false); + return; + case exprstatetype::def_0: + this->exs_type_ = exprstatetype::def_1; + this->def_expr_->assign_lhs_name(symbol_name); + //this->def_lhs_symbol_ = symbol_name; + + return; + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + /* NOT IMPLEMENTED */ + assert(false); + return; + + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_type: + case exprstatetype::expect_symbol: + case exprstatetype::expr_progress: + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return; + } + } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index 63d19f93..8e0df1ef 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -991,7 +991,7 @@ namespace xo { } /*on_expr*/ void - exprstate::on_symbol(const std::string & symbol_name, + exprstate::on_symbol(const std::string & /*symbol_name*/, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { @@ -1007,17 +1007,12 @@ namespace xo { assert(false); return; case exprstatetype::def_0: - this->exs_type_ = exprstatetype::def_1; - this->def_expr_->assign_lhs_name(symbol_name); - //this->def_lhs_symbol_ = symbol_name; - - return; case exprstatetype::def_1: case exprstatetype::def_2: case exprstatetype::def_3: case exprstatetype::def_4: case exprstatetype::def_5: - /* NOT IMPLEMENTED */ + /* unreachable */ assert(false); return; From 3f949dd3e4eea3492b21090a35eeed513688db95 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 7 Aug 2024 16:36:46 -0400 Subject: [PATCH 1325/2524] xo-reader: refactor: specialize admits_definition() for define_xs --- include/xo/reader/define_xs.hpp | 2 ++ src/reader/define_xs.cpp | 37 +++++++++++++++++++++++++++++++++ src/reader/parser.cpp | 6 +----- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index c872a58b..41bf90d4 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -20,6 +20,8 @@ namespace xo { static std::unique_ptr def_0(rp def_expr); + virtual bool admits_definition() const override; + // virtual void on_f64(..) override virtual void on_expr(ref::brw expr, exprstatestack * p_stack, rp * p_emit_expr) override; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 09148c9e..5da374ad 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -10,6 +10,43 @@ namespace xo { def_expr) {} + bool + define_xs::admits_definition() const + { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + /* unreachable */ + assert(false); + return false; + + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + /* note for def_4: + * rhs could certainly be a function body that contains + * nested defines; but then immediately-enclosing-exprstate + * would be a block + */ + return false; + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_symbol: + case exprstatetype::expect_type: + case exprstatetype::expr_progress: + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return false; + } + + return false; + } + void define_xs::on_expr(ref::brw expr, exprstatestack * /* p_stack */, diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index 8e0df1ef..e5700bdd 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -70,11 +70,7 @@ namespace xo { case exprstatetype::def_3: case exprstatetype::def_4: case exprstatetype::def_5: - /* note for def_4: - * rhs could certainly be a function body that contains - * nested defines; but then immediately-enclosing-exprstate - * would be a block - */ + /* unreachable */ return false; case exprstatetype::lparen_0: case exprstatetype::lparen_1: From 8b77fa9e99625e27d9ca1a61931dc9c350e31187 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 11:31:52 -0400 Subject: [PATCH 1326/2524] xo-reader: refactor: mv admits_symbol de- expr work -> define_xs --- include/xo/reader/define_xs.hpp | 1 + src/reader/define_xs.cpp | 32 ++++++++++++++++++++++++++++++++ src/reader/parser.cpp | 4 ++++ 3 files changed, 37 insertions(+) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 41bf90d4..162bd62a 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -21,6 +21,7 @@ namespace xo { static std::unique_ptr def_0(rp def_expr); virtual bool admits_definition() const override; + virtual bool admits_symbol() const override; // virtual void on_f64(..) override virtual void on_expr(ref::brw expr, exprstatestack * p_stack, diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 5da374ad..6fa1bc45 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -47,6 +47,38 @@ namespace xo { return false; } + bool + define_xs::admits_symbol() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + /* unreachable */ + assert(false); + return false; + + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + return false; + + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_symbol: + case exprstatetype::expect_type: + case exprstatetype::expr_progress: + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return false; + } + + return false; + } + void define_xs::on_expr(ref::brw expr, exprstatestack * /* p_stack */, diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index e5700bdd..f503932c 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -94,12 +94,16 @@ namespace xo { exprstate::admits_symbol() const { switch (exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: + return false; + case exprstatetype::def_0: case exprstatetype::def_1: case exprstatetype::def_2: case exprstatetype::def_3: case exprstatetype::def_4: case exprstatetype::def_5: + /* unreachable */ + assert(false); return false; case exprstatetype::lparen_0: From ca33241ce209f2fd63b39ea14ea2fa62a49f4d6f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 11:40:51 -0400 Subject: [PATCH 1327/2524] xo-reader: refactor: mv admits_colon -> define_xs --- include/xo/reader/define_xs.hpp | 2 ++ src/reader/define_xs.cpp | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 162bd62a..b41454e3 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -22,6 +22,8 @@ namespace xo { virtual bool admits_definition() const override; virtual bool admits_symbol() const override; + virtual bool admits_colon() const override; + // virtual void on_f64(..) override virtual void on_expr(ref::brw expr, exprstatestack * p_stack, diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 6fa1bc45..5c760e5e 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -79,6 +79,42 @@ namespace xo { return false; } + bool + define_xs::admits_colon() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + /* unreachable */ + assert(false); + return false; + + case exprstatetype::def_0: + return false; + + case exprstatetype::def_1: + return true; + + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + return false; + + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_symbol: + case exprstatetype::expect_type: + case exprstatetype::expr_progress: + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return false; + } + + return false; + } + void define_xs::on_expr(ref::brw expr, exprstatestack * /* p_stack */, From 452c224110806c04f894c6be73cc4f0ba4c5731b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 11:41:27 -0400 Subject: [PATCH 1328/2524] xo-reader: refactor: mv admits_semicolon for defexpr -> define_xs --- include/xo/reader/define_xs.hpp | 1 + src/reader/define_xs.cpp | 31 +++++++++++++++++++++++++++++++ src/reader/parser.cpp | 1 + 3 files changed, 33 insertions(+) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index b41454e3..4cd4a297 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -23,6 +23,7 @@ namespace xo { virtual bool admits_definition() const override; virtual bool admits_symbol() const override; virtual bool admits_colon() const override; + virtual bool admits_semicolon() const override; // virtual void on_f64(..) override virtual void on_expr(ref::brw expr, diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 5c760e5e..85efffea 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -115,6 +115,37 @@ namespace xo { return false; } + bool + define_xs::admits_semicolon() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + /* unreachable */ + assert(false); + return false; + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + return false; + case exprstatetype::def_5: + return true; + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_symbol: + case exprstatetype::expect_type: + case exprstatetype::expr_progress: + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return false; + } + + return false; + } + void define_xs::on_expr(ref::brw expr, exprstatestack * /* p_stack */, diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index f503932c..db116b48 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -71,6 +71,7 @@ namespace xo { case exprstatetype::def_4: case exprstatetype::def_5: /* unreachable */ + assert(false); return false; case exprstatetype::lparen_0: case exprstatetype::lparen_1: From 762aa0016a1daa5d2ac9d6bccb912741151ab012 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 12:16:59 -0400 Subject: [PATCH 1329/2524] xo-reader: refactor: mv admits_singleassign -> define_xs --- include/xo/reader/define_xs.hpp | 1 + src/reader/define_xs.cpp | 54 +++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 4cd4a297..2f3e0879 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -24,6 +24,7 @@ namespace xo { virtual bool admits_symbol() const override; virtual bool admits_colon() const override; virtual bool admits_semicolon() const override; + virtual bool admits_singleassign() const override; // virtual void on_f64(..) override virtual void on_expr(ref::brw expr, diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 85efffea..449eaeb6 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -146,6 +146,60 @@ namespace xo { return false; } + bool + define_xs::admits_singleassign() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + /* unreachable */ + assert(false); + return false; + + /* + * def foo = 1 ; + * def foo : f64 = 1 ; + * ^ ^ ^ ^ ^ ^ ^ + * | | | | | | (done) + * | | | | | def_4:expect_rhs_expression + * | | | | def_3 + * | | | def_2:expect_type + * | | def_1 + * | def_0:expect_symbol + * expect_toplevel_expression_sequence + * + * note that we skip from def_1 -> def_4 if '=' instead of ':' + */ + case exprstatetype::def_0: + return false; + + case exprstatetype::def_1: + return true; + + case exprstatetype::def_2: + return false; + + case exprstatetype::def_3: + return true; + + case exprstatetype::def_4: + case exprstatetype::def_5: + return false; + + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_symbol: + case exprstatetype::expect_type: + case exprstatetype::expr_progress: + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + /* unreachable */ + assert(false); + return false; + } + + return false; + } void define_xs::on_expr(ref::brw expr, exprstatestack * /* p_stack */, From e544491ef7241d8e757da72fbae09e74b521b538 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 12:54:50 -0400 Subject: [PATCH 1330/2524] xo-reader: refactor: mv admits_xxxparen -> define_xs --- include/xo/reader/define_xs.hpp | 2 + src/reader/define_xs.cpp | 76 +++++++++++++++++++++++++++++++++ src/reader/parser.cpp | 17 +++----- 3 files changed, 84 insertions(+), 11 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 2f3e0879..8b9d1512 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -25,6 +25,8 @@ namespace xo { virtual bool admits_colon() const override; virtual bool admits_semicolon() const override; virtual bool admits_singleassign() const override; + virtual bool admits_leftparen() const override; + virtual bool admits_rightparen() const override; // virtual void on_f64(..) override virtual void on_expr(ref::brw expr, diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 449eaeb6..239abb0f 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -200,6 +200,82 @@ namespace xo { return false; } + + bool + define_xs::admits_leftparen() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + /* unreachable */ + assert(false); + return false; + + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + /* input like + * def foo : f64 = ( + * ^ ^ ^ ^ ^ + * | | | | def_4 + * | | | def_3 + * | | def_2 + * | def_1 + * def_0 + * + * not allowed or relies on pushing another state + */ + return false; + + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_type: + case exprstatetype::expect_symbol: + case exprstatetype::expr_progress: + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return false; + } + + return false; + } + + bool + define_xs::admits_rightparen() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + /* unreachable */ + assert(false); + return false; + + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + return false; + + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_type: + case exprstatetype::expect_symbol: + case exprstatetype::expr_progress: + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return false; + } + + return false; + } + void define_xs::on_expr(ref::brw expr, exprstatestack * /* p_stack */, diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index db116b48..d777fa6b 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -310,17 +310,8 @@ namespace xo { case exprstatetype::def_3: case exprstatetype::def_4: case exprstatetype::def_5: - /* input like - * def foo : f64 = ( - * ^ ^ ^ ^ ^ - * | | | | def_4 - * | | | def_3 - * | | def_2 - * | def_1 - * def_0 - * - * not allowed or relies on pushing another state - */ + /* unreachable */ + assert(false); return false; case exprstatetype::lparen_0: @@ -357,12 +348,16 @@ namespace xo { exprstate::admits_rightparen() const { switch (exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: + return false; + case exprstatetype::def_0: case exprstatetype::def_1: case exprstatetype::def_2: case exprstatetype::def_3: case exprstatetype::def_4: case exprstatetype::def_5: + /* unreachable */ + assert(false); return false; case exprstatetype::lparen_0: From 190a0fa7199f039fa5c7bc904cab99efa4403dbb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 13:18:58 -0400 Subject: [PATCH 1331/2524] xo-reader: tidy: mv exprstate impl -> exprstate.cpp --- src/reader/define_xs.cpp | 5 + src/reader/exprstate.cpp | 1098 +++++++++++++++++++++++++++++++++++++- src/reader/parser.cpp | 1093 +------------------------------------ 3 files changed, 1102 insertions(+), 1094 deletions(-) diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 239abb0f..e4df8cea 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -4,6 +4,11 @@ namespace xo { namespace scm { + std::unique_ptr + define_xs::def_0(rp def_expr) { + return std::make_unique(define_xs(def_expr)); + } + define_xs::define_xs(rp def_expr) : exprstate(exprstatetype::def_0, nullptr /*gen_expr*/, diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index c6b2ef36..f58be6bd 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -2,12 +2,1104 @@ #include "exprstate.hpp" #include "define_xs.hpp" +//#include "xo/expression/DefineExpr.hpp" +#include "xo/expression/Constant.hpp" +//#include "xo/expression/ConvertExpr.hpp" +#include "xo/reflect/Reflect.hpp" namespace xo { + using xo::ast::Constant; + using xo::reflect::Reflect; + using xo::reflect::TypeDescr; + namespace scm { - std::unique_ptr - define_xs::def_0(rp def_expr) { - return std::make_unique(define_xs(def_expr)); + const char * + exprstatetype_descr(exprstatetype x) { + switch (x) { + case exprstatetype::invalid: + return "?invalid"; + case exprstatetype::expect_toplevel_expression_sequence: + return "expect_toplevel_expression_sequence"; + case exprstatetype::def_0: + return "def_0"; + case exprstatetype::def_1: + return "def_1"; + case exprstatetype::def_2: + return "def_2"; + case exprstatetype::def_3: + return "def_3"; + case exprstatetype::def_4: + return "def_4"; + case exprstatetype::def_5: + return "def_5"; + case exprstatetype::lparen_0: + return "lparen_0"; + case exprstatetype::lparen_1: + return "lparen_1"; + case exprstatetype::expect_rhs_expression: + return "expect_rhs_expression"; + case exprstatetype::expect_symbol: + return "expect_symbol"; + case exprstatetype::expect_type: + return "expect_type"; + case exprstatetype::expr_progress: + return "expr_progress"; + case exprstatetype::n_exprstatetype: + break; + } + + return "???"; + } + + bool + exprstate::admits_definition() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + return true; + + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + /* unreachable */ + assert(false); + return false; + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + case exprstatetype::expect_rhs_expression: + return false; + case exprstatetype::expect_symbol: + case exprstatetype::expect_type: + return false; + case exprstatetype::expr_progress: + return false; + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + return false; + } + + return false; + } + + bool + exprstate::admits_symbol() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + return false; + + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + /* unreachable */ + assert(false); + return false; + + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + case exprstatetype::expect_rhs_expression: + /* treat symbol as variable name */ + return true; + + case exprstatetype::expect_symbol: + return true; + + case exprstatetype::expect_type: + /* treat symbol as typename */ + return true; + + case exprstatetype::expr_progress: + return false; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + return false; + } + + return false; + } + + bool + exprstate::admits_colon() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + case exprstatetype::def_0: + return false; + + case exprstatetype::def_1: + return true; + + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + case exprstatetype::expect_rhs_expression: + /* rhs-expressions (or expressions for that matter) + * may not begin with a colon + */ + case exprstatetype::expect_symbol: + case exprstatetype::expect_type: + return false; + + case exprstatetype::expr_progress: + return false; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + return false; + } + + return false; + } + + bool + exprstate::admits_semicolon() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + return false; + case exprstatetype::def_5: + return true; + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_symbol: + case exprstatetype::expect_type: + return false; + case exprstatetype::expr_progress: + return true; + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + return false; + } + + return false; + } + + bool + exprstate::admits_singleassign() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + + /* + * def foo = 1 ; + * def foo : f64 = 1 ; + * ^ ^ ^ ^ ^ ^ ^ + * | | | | | | (done) + * | | | | | def_4:expect_rhs_expression + * | | | | def_3 + * | | | def_2:expect_type + * | | def_1 + * | def_0:expect_symbol + * expect_toplevel_expression_sequence + * + * note that we skip from def_1 -> def_4 if '=' instead of ':' + */ + case exprstatetype::def_0: + return false; + + case exprstatetype::def_1: + return true; + + case exprstatetype::def_2: + return false; + + case exprstatetype::def_3: + return true; + + case exprstatetype::def_4: + case exprstatetype::def_5: + + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + case exprstatetype::expect_rhs_expression: + /* rhs-expressions (or expressions for that matter) + * may not begin with singleassign '=' + */ + case exprstatetype::expect_symbol: + case exprstatetype::expect_type: + return false; + + case exprstatetype::expr_progress: + return false; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + return false; + } + + return false; + } + + bool + exprstate::admits_f64() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + return false; + + case exprstatetype::lparen_0: + return true; + + case exprstatetype::lparen_1: + return false; + + case exprstatetype::expect_rhs_expression: + return true; + + case exprstatetype::expect_symbol: + case exprstatetype::expect_type: + return false; + + case exprstatetype::expr_progress: + return false; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + return false; + } + + return false; + } + + bool + exprstate::admits_leftparen() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + /* input like + * (function(blah...)) + * not allowed at toplevel; + * creates ambiguity e.g. consider + * x := foo + * (bar) + * + * is rhs 'foo' or 'foo(bar)' + */ + return false; + + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + /* unreachable */ + assert(false); + return false; + + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + /* unreachable */ + assert(false); + return false; + + case exprstatetype::expect_rhs_expression: + /* can always begin non-toplevel expression with '(' */ + return true; + + case exprstatetype::expect_type: + return false; + + case exprstatetype::expect_symbol: + return false; + + case exprstatetype::expr_progress: + /* todo: will parse as function call */ + return false; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return false; + } + + return false; + } + + bool + exprstate::admits_rightparen() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + return false; + + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + /* unreachable */ + assert(false); + return false; + + case exprstatetype::lparen_0: + /* unreachable -- will have pushed expect_rhs_expression */ + assert(false); + return false; + + case exprstatetype::lparen_1: + return true; + + case exprstatetype::expect_rhs_expression: + return false; + + case exprstatetype::expect_type: + return false; + + case exprstatetype::expect_symbol: + return false; + + case exprstatetype::expr_progress: + /* satisfies expression form */ + return true; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return false; + } + + return false; + } + + void + exprstate::on_def(exprstatestack * p_stack) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_def"; + + /* lots of illegal states */ + if (!this->admits_definition()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected keyword 'def' for parsing state", + xtag("state", *this))); + } + + p_stack->push_exprstate + (define_xs::def_0(DefineExprAccess::make_empty())); + + /* todo: replace: + * expect_symbol_or_function_signature() + */ + p_stack->push_exprstate(exprstate::expect_symbol()); + + /* keyword 'def' introduces a definition: + * def pi : f64 = 3.14159265 + * def sq(x : f64) -> f64 { (x * x) } + */ + } + + void + exprstate::on_symbol(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("exstype", p_stack->top_exprstate().exs_type())); + + constexpr const char * self_name = "exprstate::on_symbol"; + + if (!this->admits_symbol()) { + throw std::runtime_error + (tostr(self_name, + ": unexpected symbol-token for parsing state", + xtag("symbol", tk), + xtag("state", *this))); + } + + switch (this->exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + throw std::runtime_error + (tostr(self_name, + ": unexpected symbol-token at top-level", + " (expecting decl|def)", + xtag("symbol", tk))); + break; + + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + /* unreachable */ + assert(false); + return; + + case exprstatetype::lparen_0: + /* todo: variable reference */ + assert(false); + break; + + case exprstatetype::lparen_1: + /* unreachable */ + + assert(false); + break; + + case exprstatetype::expect_rhs_expression: + { + /* various possibilities when looking for rhs expression: + * + * x := y // (1) + * x := f(a) // (2) + * x := f(a,b) // (3) + * + * need lookahead token following symbol to distinguish + * between (1) (symbol completes rhs expression) + * and {(2), (3)} (symbol is function call) + */ + + /* have to do pop first, before sending symbol to + * the o.g. symbol-requester + */ +#ifdef NOT_YET + p_stack->push_exprstate(exprstate(exprstatetype::expr_progress, + Variable::make(name, type))); +#endif + +#ifdef LATER + p_stack->pop_exprstate(); + p_stack->top_exprstate().on_symbol(tk.text(), + p_stack, p_emit_expr); +#endif + return; + } + + case exprstatetype::expect_symbol: + { + /* have to do pop first, before sending symbol to + * the o.g. symbol-requester + */ + std::unique_ptr self = p_stack->pop_exprstate(); + + p_stack->top_exprstate().on_symbol(tk.text(), + p_stack, p_emit_expr); + return; + } + + case exprstatetype::expect_type: { + TypeDescr td = nullptr; + + /* TODO: replace with typetable lookup */ + + if (tk.text() == "f64") + td = Reflect::require(); + else if(tk.text() == "f32") + td = Reflect::require(); + else if(tk.text() == "i16") + td = Reflect::require(); + else if(tk.text() == "i32") + td = Reflect::require(); + else if(tk.text() == "i64") + td = Reflect::require(); + + if (!td) { + throw std::runtime_error + (tostr(self_name, + ": unknown type name", + " (expecting f64|f32|i16|i32|i64)", + xtag("typename", tk.text()))); + } + + std::unique_ptr self = p_stack->pop_exprstate(); + p_stack->top_exprstate().on_typedescr(td, p_stack, p_emit_expr); + return; + } + + case exprstatetype::expr_progress: + /* illegal input, e.g. + * foo bar + */ + assert(false); + return; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return; + } + } /*on_symbol*/ + + void + exprstate::on_typedescr(TypeDescr td, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + /* returning type description to somethign that wants it */ + + switch (this->exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + case exprstatetype::def_0: + case exprstatetype::def_1: + /* NOT IMPLEMENTED */ + assert(false); + return; + + case exprstatetype::def_2: + this->exs_type_ = exprstatetype::def_3; + this->cvt_expr_ = ConvertExprAccess::make(td /*dest_type*/, + nullptr /*source_expr*/); + this->def_expr_->assign_rhs(this->cvt_expr_); + //this->def_lhs_td_ = td; + + return; + + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + /* NOT IMPLEMENTED */ + assert(false); + return; + + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + assert(false); + return; + + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_type: + case exprstatetype::expect_symbol: + /* unreachable + * (this exprstate issues pop instruction from exprstate::on_input() + */ + assert(false); + return; + + case exprstatetype::expr_progress: + assert(false); + return; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return; + } + } + + void + exprstate::on_colon(exprstatestack * p_stack) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_colon"; + + /* lots of illegal states */ + if (!this->admits_colon()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected colon for parsing state", + xtag("state", *this))); + } + + if (this->exs_type_ == exprstatetype::def_1) { + this->exs_type_ = exprstatetype::def_2; + + p_stack->push_exprstate(exprstate::expect_type()); + } else { + assert(false); + } + } + + void + exprstate::on_semicolon(exprstatestack * p_stack, + rp * p_emit_expr) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_semicolon"; + + if (!this->admits_semicolon()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected semicolon for parsing state", + xtag("state", *this))); + } + + if (this->exs_type_ == exprstatetype::expr_progress) { + rp expr = this->gen_expr_; + + std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + + p_stack->top_exprstate().on_expr(expr, + p_stack, + p_emit_expr); + /* control here on input like: + * (1.234; + * + * a. '(' sets up stack [lparen_0:expect_rhs_expression] + * (see exprstate::on_leftparen()) + * b. 1.234 pushes (in case operators) [lparen_0:expect_rhs_expression:expr_progress] + * (see exprstate::on_f64()) + * c. semicolon completes expr_progress [lparen_0:expect_rhs_expression] + * deliver expresssion to expect_rhs_expression.on_expr() + * (see exprstate::on_expr()) + * d. expr_rhs_expression forwards expression to [lparen_0] + * e. lparen_0 advances to [lparen_1] + * f. now deliver semicolon; [lparen_1] rejects + */ + + p_stack->top_exprstate().on_semicolon(p_stack, p_emit_expr); + } else if (this->exs_type_ == exprstatetype::def_5) { + rp expr = this->def_expr_; + + std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + + p_stack->top_exprstate().on_expr(expr, + p_stack, + p_emit_expr); + } else { + assert(false); + } + } + + void + exprstate::on_singleassign(exprstatestack * p_stack) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_singleassign"; + + if (!this->admits_singleassign()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected equals for parsing state", + xtag("state", *this))); + } + + if ((this->exs_type_ == exprstatetype::def_1) + || (this->exs_type_ == exprstatetype::def_3)) + { + this->exs_type_ = exprstatetype::def_4; + + p_stack->push_exprstate(exprstate::expect_rhs_expression()); + } else { + assert(false); + } + } + + void + exprstate::on_leftparen(exprstatestack * p_stack, + rp * /*p_emit_expr*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_leftparen"; + + if (!this->admits_leftparen()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected leftparen '(' for parsing state", + xtag("state", *this))); + } + + if (this->exs_type_ == exprstatetype::expect_rhs_expression) { + /* push lparen_0 to remember to look for subsequent rightparen. */ + p_stack->push_exprstate(exprstate::lparen_0()); + p_stack->push_exprstate(exprstate::expect_rhs_expression()); + } + } + + void + exprstate::on_rightparen(exprstatestack * p_stack, + rp * p_emit_expr) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_rightparen"; + + if (!this->admits_rightparen()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected rightparen ')' for parsing state", + xtag("state", *this))); + } + + if (this->exs_type_ == exprstatetype::expr_progress) { + /* stack may be something like: + * + * lparen_0 + * expect_rhs_expression + * expr_progress + * <-- rightparen + * + * 1. rightparen completes expression-in-progress + * 2. rightparen must then match innermost waiting lparen_0 + */ + + /* right paren confirms stack expression */ + rp expr = this->gen_expr_; + + std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + + if (p_stack->empty()) { + throw std::runtime_error(tostr(self_name, + ": expected non-empty parsing stack")); + } + + log && log(xtag("stack", p_stack)); + + p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); + + /* now deliver rightparen */ + p_stack->top_exprstate().on_rightparen(p_stack, p_emit_expr); + } else if (this->exs_type_ == exprstatetype::lparen_1) { + rp expr = this->gen_expr_; + + std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + + p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); + } + } + + void + exprstate::on_f64(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_f64"; + + if (!this->admits_f64()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected floating-point literal for parsing state", + xtag("state", *this))); + } + + if (this->exs_type_ == exprstatetype::expect_rhs_expression) { + /* e.g. + * def pi = 3.14159265; + * \---tk---/ + */ + p_stack->push_exprstate + (exprstate::make_expr_progress + (Constant::make(tk.f64_value()))); + } else { + assert(false); + } + } + + void + exprstate::on_input(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + log && log(xtag("tk", tk)); + log && log(xtag("state", *this)); + + switch (tk.tk_type()) { + + case tokentype::tk_def: + this->on_def(p_stack); + return; + + case tokentype::tk_i64: + assert(false); + return; + + case tokentype::tk_f64: + this->on_f64(tk, p_stack, p_emit_expr); + return; + + case tokentype::tk_string: + assert(false); + return; + + case tokentype::tk_symbol: + this->on_symbol(tk, p_stack, p_emit_expr); + return; + + case tokentype::tk_leftparen: + this->on_leftparen(p_stack, p_emit_expr); + return; + + case tokentype::tk_rightparen: + this->on_rightparen(p_stack, p_emit_expr); + return; + + case tokentype::tk_leftbracket: + case tokentype::tk_rightbracket: + case tokentype::tk_leftbrace: + case tokentype::tk_rightbrace: + + case tokentype::tk_leftangle: + case tokentype::tk_rightangle: + case tokentype::tk_dot: + case tokentype::tk_comma: + assert(false); + return; + + case tokentype::tk_colon: + this->on_colon(p_stack); + return; + + case tokentype::tk_doublecolon: + assert(false); + return; + + case tokentype::tk_semicolon: + this->on_semicolon(p_stack, p_emit_expr); + return; + + case tokentype::tk_singleassign: + this->on_singleassign(p_stack); + return; + + case tokentype::tk_assign: + case tokentype::tk_yields: + + case tokentype::tk_type: + case tokentype::tk_lambda: + case tokentype::tk_if: + case tokentype::tk_let: + + case tokentype::tk_in: + case tokentype::tk_end: + assert(false); + return; + + case tokentype::tk_invalid: + case tokentype::n_tokentype: + assert(false); + return; + } + + assert(false); + } + + void + exprstate::on_expr(ref::brw expr, + exprstatestack * p_stack, + rp * p_emit_expr) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("exstype", this->exs_type_), + xtag("expr", expr)); + + switch (this->exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + /* toplevel expression sequence accepts an + * arbitrary number of expressions. + * + * parser::include_token() returns + */ + + *p_emit_expr = expr.promote(); + return; + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + /* unreachable. see define_xs::on_expr() */ + assert(false); + return; + + case exprstatetype::lparen_0: { + this->exs_type_ = exprstatetype::lparen_1; /* wants on_rightparen */ + p_stack->push_exprstate(exprstate::make_expr_progress(expr.promote())); + + return; + } + + case exprstatetype::lparen_1: { + this->gen_expr_ = expr.promote(); + + /* expect immediate incoming call, this time to on_rightparen() */ + return; + } + + case exprstatetype::expect_rhs_expression: { + + std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + + p_stack->top_exprstate().on_expr(expr, + p_stack, + p_emit_expr); + + return; + } + + case exprstatetype::expect_type: + case exprstatetype::expect_symbol: + /* unreachable + * (this exprstate issues pop instruction from exprstate::on_input() + */ + assert(false); + return; + case exprstatetype::expr_progress: + /* consecutive expressions isn't legal + */ + assert(false); + return; + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return; + } + } /*on_expr*/ + + void + exprstate::on_symbol(const std::string & /*symbol_name*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + switch(this->exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + /* toplevel expression sequence accepts an + * arbitrary number of expressions. + * + * parser::include_token() returns + */ + + /* NOT IMPLEMENTED */ + assert(false); + return; + case exprstatetype::def_0: + case exprstatetype::def_1: + case exprstatetype::def_2: + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + /* unreachable */ + assert(false); + return; + + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + /* NOT IMPLEMENTED */ + assert(false); + return; + + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_type: + case exprstatetype::expect_symbol: + /* unreachable + * (this exprstate issues pop instruction from exprstate::on_input() + */ + assert(false); + return; + case exprstatetype::expr_progress: + assert(false); + return; + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return; + } + } + + void + exprstate::print(std::ostream & os) const { + os << ""; + } + + // ----- exprstatestack ----- + + exprstate & + exprstatestack::top_exprstate() { + std::size_t z = stack_.size(); + + if (z == 0) { + throw std::runtime_error + ("parser::top_exprstate: unexpected empty stack"); + } + + return *(stack_[z-1]); + } + + void + exprstatestack::push_exprstate(std::unique_ptr exs) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag), + xtag("exs", *exs)); + + std::size_t z = stack_.size(); + + stack_.resize(z+1); + + stack_[z] = std::move(exs); + } + + std::unique_ptr + exprstatestack::pop_exprstate() { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag), + xtag("top.exstype", top_exprstate().exs_type())); + + std::size_t z = stack_.size(); + + if (z > 0) { + std::unique_ptr top = std::move(stack_[z-1]); + + stack_.resize(z-1); + + return top; + } else { + return nullptr; + } + } + + void + exprstatestack::print(std::ostream & os) const { + os << "" << std::endl; } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index d777fa6b..f056eeac 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -15,1100 +15,11 @@ namespace xo { using xo::ast::Expression; //using xo::ast::DefineExpr; //using xo::ast::ConvertExpr; - using xo::ast::Constant; - using xo::reflect::Reflect; + //using xo::ast::Constant; + //using xo::reflect::Reflect; using xo::reflect::TypeDescr; namespace scm { - const char * - exprstatetype_descr(exprstatetype x) { - switch (x) { - case exprstatetype::invalid: - return "?invalid"; - case exprstatetype::expect_toplevel_expression_sequence: - return "expect_toplevel_expression_sequence"; - case exprstatetype::def_0: - return "def_0"; - case exprstatetype::def_1: - return "def_1"; - case exprstatetype::def_2: - return "def_2"; - case exprstatetype::def_3: - return "def_3"; - case exprstatetype::def_4: - return "def_4"; - case exprstatetype::def_5: - return "def_5"; - case exprstatetype::lparen_0: - return "lparen_0"; - case exprstatetype::lparen_1: - return "lparen_1"; - case exprstatetype::expect_rhs_expression: - return "expect_rhs_expression"; - case exprstatetype::expect_symbol: - return "expect_symbol"; - case exprstatetype::expect_type: - return "expect_type"; - case exprstatetype::expr_progress: - return "expr_progress"; - case exprstatetype::n_exprstatetype: - break; - } - - return "???"; - } - - bool - exprstate::admits_definition() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - return true; - - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - /* unreachable */ - assert(false); - return false; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - case exprstatetype::expect_rhs_expression: - return false; - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - return false; - case exprstatetype::expr_progress: - return false; - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - return false; - } - - return false; - } - - bool - exprstate::admits_symbol() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - return false; - - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - /* unreachable */ - assert(false); - return false; - - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - case exprstatetype::expect_rhs_expression: - /* treat symbol as variable name */ - return true; - - case exprstatetype::expect_symbol: - return true; - - case exprstatetype::expect_type: - /* treat symbol as typename */ - return true; - - case exprstatetype::expr_progress: - return false; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - return false; - } - - return false; - } - - bool - exprstate::admits_colon() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::def_0: - return false; - - case exprstatetype::def_1: - return true; - - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - case exprstatetype::expect_rhs_expression: - /* rhs-expressions (or expressions for that matter) - * may not begin with a colon - */ - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - return false; - - case exprstatetype::expr_progress: - return false; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - return false; - } - - return false; - } - - bool - exprstate::admits_semicolon() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - return false; - case exprstatetype::def_5: - return true; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - return false; - case exprstatetype::expr_progress: - return true; - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - return false; - } - - return false; - } - - bool - exprstate::admits_singleassign() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - - /* - * def foo = 1 ; - * def foo : f64 = 1 ; - * ^ ^ ^ ^ ^ ^ ^ - * | | | | | | (done) - * | | | | | def_4:expect_rhs_expression - * | | | | def_3 - * | | | def_2:expect_type - * | | def_1 - * | def_0:expect_symbol - * expect_toplevel_expression_sequence - * - * note that we skip from def_1 -> def_4 if '=' instead of ':' - */ - case exprstatetype::def_0: - return false; - - case exprstatetype::def_1: - return true; - - case exprstatetype::def_2: - return false; - - case exprstatetype::def_3: - return true; - - case exprstatetype::def_4: - case exprstatetype::def_5: - - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - case exprstatetype::expect_rhs_expression: - /* rhs-expressions (or expressions for that matter) - * may not begin with singleassign '=' - */ - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - return false; - - case exprstatetype::expr_progress: - return false; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - return false; - } - - return false; - } - - bool - exprstate::admits_f64() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - return false; - - case exprstatetype::lparen_0: - return true; - - case exprstatetype::lparen_1: - return false; - - case exprstatetype::expect_rhs_expression: - return true; - - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - return false; - - case exprstatetype::expr_progress: - return false; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - return false; - } - - return false; - } - - bool - exprstate::admits_leftparen() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - /* input like - * (function(blah...)) - * not allowed at toplevel; - * creates ambiguity e.g. consider - * x := foo - * (bar) - * - * is rhs 'foo' or 'foo(bar)' - */ - return false; - - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - /* unreachable */ - assert(false); - return false; - - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - /* unreachable */ - assert(false); - return false; - - case exprstatetype::expect_rhs_expression: - /* can always begin non-toplevel expression with '(' */ - return true; - - case exprstatetype::expect_type: - return false; - - case exprstatetype::expect_symbol: - return false; - - case exprstatetype::expr_progress: - /* todo: will parse as function call */ - return false; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - assert(false); - return false; - } - - return false; - } - - bool - exprstate::admits_rightparen() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - return false; - - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - /* unreachable */ - assert(false); - return false; - - case exprstatetype::lparen_0: - /* unreachable -- will have pushed expect_rhs_expression */ - assert(false); - return false; - - case exprstatetype::lparen_1: - return true; - - case exprstatetype::expect_rhs_expression: - return false; - - case exprstatetype::expect_type: - return false; - - case exprstatetype::expect_symbol: - return false; - - case exprstatetype::expr_progress: - /* satisfies expression form */ - return true; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - assert(false); - return false; - } - - return false; - } - - void - exprstate::on_def(exprstatestack * p_stack) { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - - constexpr const char * self_name = "exprstate::on_def"; - - /* lots of illegal states */ - if (!this->admits_definition()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected keyword 'def' for parsing state", - xtag("state", *this))); - } - - p_stack->push_exprstate - (define_xs::def_0(DefineExprAccess::make_empty())); - - /* todo: replace: - * expect_symbol_or_function_signature() - */ - p_stack->push_exprstate(exprstate::expect_symbol()); - - /* keyword 'def' introduces a definition: - * def pi : f64 = 3.14159265 - * def sq(x : f64) -> f64 { (x * x) } - */ - } - - void - exprstate::on_symbol(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) - { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - - log && log(xtag("exstype", p_stack->top_exprstate().exs_type())); - - constexpr const char * self_name = "exprstate::on_symbol"; - - if (!this->admits_symbol()) { - throw std::runtime_error - (tostr(self_name, - ": unexpected symbol-token for parsing state", - xtag("symbol", tk), - xtag("state", *this))); - } - - switch (this->exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - throw std::runtime_error - (tostr(self_name, - ": unexpected symbol-token at top-level", - " (expecting decl|def)", - xtag("symbol", tk))); - break; - - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - /* unreachable */ - assert(false); - return; - - case exprstatetype::lparen_0: - /* todo: variable reference */ - assert(false); - break; - - case exprstatetype::lparen_1: - /* unreachable */ - - assert(false); - break; - - case exprstatetype::expect_rhs_expression: - { - /* various possibilities when looking for rhs expression: - * - * x := y // (1) - * x := f(a) // (2) - * x := f(a,b) // (3) - * - * need lookahead token following symbol to distinguish - * between (1) (symbol completes rhs expression) - * and {(2), (3)} (symbol is function call) - */ - - /* have to do pop first, before sending symbol to - * the o.g. symbol-requester - */ -#ifdef NOT_YET - p_stack->push_exprstate(exprstate(exprstatetype::expr_progress, - Variable::make(name, type))); -#endif - -#ifdef LATER - p_stack->pop_exprstate(); - p_stack->top_exprstate().on_symbol(tk.text(), - p_stack, p_emit_expr); -#endif - return; - } - - case exprstatetype::expect_symbol: - { - /* have to do pop first, before sending symbol to - * the o.g. symbol-requester - */ - std::unique_ptr self = p_stack->pop_exprstate(); - - p_stack->top_exprstate().on_symbol(tk.text(), - p_stack, p_emit_expr); - return; - } - - case exprstatetype::expect_type: { - TypeDescr td = nullptr; - - /* TODO: replace with typetable lookup */ - - if (tk.text() == "f64") - td = Reflect::require(); - else if(tk.text() == "f32") - td = Reflect::require(); - else if(tk.text() == "i16") - td = Reflect::require(); - else if(tk.text() == "i32") - td = Reflect::require(); - else if(tk.text() == "i64") - td = Reflect::require(); - - if (!td) { - throw std::runtime_error - (tostr(self_name, - ": unknown type name", - " (expecting f64|f32|i16|i32|i64)", - xtag("typename", tk.text()))); - } - - std::unique_ptr self = p_stack->pop_exprstate(); - p_stack->top_exprstate().on_typedescr(td, p_stack, p_emit_expr); - return; - } - - case exprstatetype::expr_progress: - /* illegal input, e.g. - * foo bar - */ - assert(false); - return; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - assert(false); - return; - } - } /*on_symbol*/ - - void - exprstate::on_typedescr(TypeDescr td, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) - { - /* returning type description to somethign that wants it */ - - switch (this->exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::def_0: - case exprstatetype::def_1: - /* NOT IMPLEMENTED */ - assert(false); - return; - - case exprstatetype::def_2: - this->exs_type_ = exprstatetype::def_3; - this->cvt_expr_ = ConvertExprAccess::make(td /*dest_type*/, - nullptr /*source_expr*/); - this->def_expr_->assign_rhs(this->cvt_expr_); - //this->def_lhs_td_ = td; - - return; - - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - /* NOT IMPLEMENTED */ - assert(false); - return; - - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - assert(false); - return; - - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_type: - case exprstatetype::expect_symbol: - /* unreachable - * (this exprstate issues pop instruction from exprstate::on_input() - */ - assert(false); - return; - - case exprstatetype::expr_progress: - assert(false); - return; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - assert(false); - return; - } - } - - void - exprstate::on_colon(exprstatestack * p_stack) { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - - constexpr const char * self_name = "exprstate::on_colon"; - - /* lots of illegal states */ - if (!this->admits_colon()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected colon for parsing state", - xtag("state", *this))); - } - - if (this->exs_type_ == exprstatetype::def_1) { - this->exs_type_ = exprstatetype::def_2; - - p_stack->push_exprstate(exprstate::expect_type()); - } else { - assert(false); - } - } - - void - exprstate::on_semicolon(exprstatestack * p_stack, - rp * p_emit_expr) - { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - - constexpr const char * self_name = "exprstate::on_semicolon"; - - if (!this->admits_semicolon()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected semicolon for parsing state", - xtag("state", *this))); - } - - if (this->exs_type_ == exprstatetype::expr_progress) { - rp expr = this->gen_expr_; - - std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ - - p_stack->top_exprstate().on_expr(expr, - p_stack, - p_emit_expr); - /* control here on input like: - * (1.234; - * - * a. '(' sets up stack [lparen_0:expect_rhs_expression] - * (see exprstate::on_leftparen()) - * b. 1.234 pushes (in case operators) [lparen_0:expect_rhs_expression:expr_progress] - * (see exprstate::on_f64()) - * c. semicolon completes expr_progress [lparen_0:expect_rhs_expression] - * deliver expresssion to expect_rhs_expression.on_expr() - * (see exprstate::on_expr()) - * d. expr_rhs_expression forwards expression to [lparen_0] - * e. lparen_0 advances to [lparen_1] - * f. now deliver semicolon; [lparen_1] rejects - */ - - p_stack->top_exprstate().on_semicolon(p_stack, p_emit_expr); - } else if (this->exs_type_ == exprstatetype::def_5) { - rp expr = this->def_expr_; - - std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ - - p_stack->top_exprstate().on_expr(expr, - p_stack, - p_emit_expr); - } else { - assert(false); - } - } - - void - exprstate::on_singleassign(exprstatestack * p_stack) { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - - constexpr const char * self_name = "exprstate::on_singleassign"; - - if (!this->admits_singleassign()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected equals for parsing state", - xtag("state", *this))); - } - - if ((this->exs_type_ == exprstatetype::def_1) - || (this->exs_type_ == exprstatetype::def_3)) - { - this->exs_type_ = exprstatetype::def_4; - - p_stack->push_exprstate(exprstate::expect_rhs_expression()); - } else { - assert(false); - } - } - - void - exprstate::on_leftparen(exprstatestack * p_stack, - rp * /*p_emit_expr*/) - { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - - constexpr const char * self_name = "exprstate::on_leftparen"; - - if (!this->admits_leftparen()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected leftparen '(' for parsing state", - xtag("state", *this))); - } - - if (this->exs_type_ == exprstatetype::expect_rhs_expression) { - /* push lparen_0 to remember to look for subsequent rightparen. */ - p_stack->push_exprstate(exprstate::lparen_0()); - p_stack->push_exprstate(exprstate::expect_rhs_expression()); - } - } - - void - exprstate::on_rightparen(exprstatestack * p_stack, - rp * p_emit_expr) - { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - - constexpr const char * self_name = "exprstate::on_rightparen"; - - if (!this->admits_rightparen()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected rightparen ')' for parsing state", - xtag("state", *this))); - } - - if (this->exs_type_ == exprstatetype::expr_progress) { - /* stack may be something like: - * - * lparen_0 - * expect_rhs_expression - * expr_progress - * <-- rightparen - * - * 1. rightparen completes expression-in-progress - * 2. rightparen must then match innermost waiting lparen_0 - */ - - /* right paren confirms stack expression */ - rp expr = this->gen_expr_; - - std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ - - if (p_stack->empty()) { - throw std::runtime_error(tostr(self_name, - ": expected non-empty parsing stack")); - } - - log && log(xtag("stack", p_stack)); - - p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); - - /* now deliver rightparen */ - p_stack->top_exprstate().on_rightparen(p_stack, p_emit_expr); - } else if (this->exs_type_ == exprstatetype::lparen_1) { - rp expr = this->gen_expr_; - - std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ - - p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); - } - } - - void - exprstate::on_f64(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) - { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - - constexpr const char * self_name = "exprstate::on_f64"; - - if (!this->admits_f64()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected floating-point literal for parsing state", - xtag("state", *this))); - } - - if (this->exs_type_ == exprstatetype::expect_rhs_expression) { - /* e.g. - * def pi = 3.14159265; - * \---tk---/ - */ - p_stack->push_exprstate - (exprstate::make_expr_progress - (Constant::make(tk.f64_value()))); - } else { - assert(false); - } - } - - void - exprstate::on_input(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) - { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - log && log(xtag("tk", tk)); - log && log(xtag("state", *this)); - - switch (tk.tk_type()) { - - case tokentype::tk_def: - this->on_def(p_stack); - return; - - case tokentype::tk_i64: - assert(false); - return; - - case tokentype::tk_f64: - this->on_f64(tk, p_stack, p_emit_expr); - return; - - case tokentype::tk_string: - assert(false); - return; - - case tokentype::tk_symbol: - this->on_symbol(tk, p_stack, p_emit_expr); - return; - - case tokentype::tk_leftparen: - this->on_leftparen(p_stack, p_emit_expr); - return; - - case tokentype::tk_rightparen: - this->on_rightparen(p_stack, p_emit_expr); - return; - - case tokentype::tk_leftbracket: - case tokentype::tk_rightbracket: - case tokentype::tk_leftbrace: - case tokentype::tk_rightbrace: - - case tokentype::tk_leftangle: - case tokentype::tk_rightangle: - case tokentype::tk_dot: - case tokentype::tk_comma: - assert(false); - return; - - case tokentype::tk_colon: - this->on_colon(p_stack); - return; - - case tokentype::tk_doublecolon: - assert(false); - return; - - case tokentype::tk_semicolon: - this->on_semicolon(p_stack, p_emit_expr); - return; - - case tokentype::tk_singleassign: - this->on_singleassign(p_stack); - return; - - case tokentype::tk_assign: - case tokentype::tk_yields: - - case tokentype::tk_type: - case tokentype::tk_lambda: - case tokentype::tk_if: - case tokentype::tk_let: - - case tokentype::tk_in: - case tokentype::tk_end: - assert(false); - return; - - case tokentype::tk_invalid: - case tokentype::n_tokentype: - assert(false); - return; - } - - assert(false); - } - - void - exprstate::on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * p_emit_expr) - { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - - log && log(xtag("exstype", this->exs_type_), - xtag("expr", expr)); - - switch (this->exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - /* toplevel expression sequence accepts an - * arbitrary number of expressions. - * - * parser::include_token() returns - */ - - *p_emit_expr = expr.promote(); - return; - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - /* unreachable. see define_xs::on_expr() */ - assert(false); - return; - - case exprstatetype::lparen_0: { - this->exs_type_ = exprstatetype::lparen_1; /* wants on_rightparen */ - p_stack->push_exprstate(exprstate::make_expr_progress(expr.promote())); - - return; - } - - case exprstatetype::lparen_1: { - this->gen_expr_ = expr.promote(); - - /* expect immediate incoming call, this time to on_rightparen() */ - return; - } - - case exprstatetype::expect_rhs_expression: { - - std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ - - p_stack->top_exprstate().on_expr(expr, - p_stack, - p_emit_expr); - - return; - } - - case exprstatetype::expect_type: - case exprstatetype::expect_symbol: - /* unreachable - * (this exprstate issues pop instruction from exprstate::on_input() - */ - assert(false); - return; - case exprstatetype::expr_progress: - /* consecutive expressions isn't legal - */ - assert(false); - return; - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - assert(false); - return; - } - } /*on_expr*/ - - void - exprstate::on_symbol(const std::string & /*symbol_name*/, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) - { - switch(this->exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - /* toplevel expression sequence accepts an - * arbitrary number of expressions. - * - * parser::include_token() returns - */ - - /* NOT IMPLEMENTED */ - assert(false); - return; - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - /* unreachable */ - assert(false); - return; - - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - /* NOT IMPLEMENTED */ - assert(false); - return; - - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_type: - case exprstatetype::expect_symbol: - /* unreachable - * (this exprstate issues pop instruction from exprstate::on_input() - */ - assert(false); - return; - case exprstatetype::expr_progress: - assert(false); - return; - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - assert(false); - return; - } - } - - void - exprstate::print(std::ostream & os) const { - os << ""; - } - - // ----- exprstatestack ----- - - exprstate & - exprstatestack::top_exprstate() { - std::size_t z = stack_.size(); - - if (z == 0) { - throw std::runtime_error - ("parser::top_exprstate: unexpected empty stack"); - } - - return *(stack_[z-1]); - } - - void - exprstatestack::push_exprstate(std::unique_ptr exs) { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag), - xtag("exs", *exs)); - - std::size_t z = stack_.size(); - - stack_.resize(z+1); - - stack_[z] = std::move(exs); - } - - std::unique_ptr - exprstatestack::pop_exprstate() { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag), - xtag("top.exstype", top_exprstate().exs_type())); - - std::size_t z = stack_.size(); - - if (z > 0) { - std::unique_ptr top = std::move(stack_[z-1]); - - stack_.resize(z-1); - - return top; - } else { - return nullptr; - } - } - - void - exprstatestack::print(std::ostream & os) const { - os << "" << std::endl; - } - // ----- parser ----- bool From b6a94bb61c840bdb600663d4b1faf7eadb432588 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 13:25:56 -0400 Subject: [PATCH 1332/2524] xo-reader: refactor: mv on_typedescr def-expr impl -> define_xs --- include/xo/reader/define_xs.hpp | 3 +++ src/reader/define_xs.cpp | 48 +++++++++++++++++++++++++++++++++ src/reader/exprstate.cpp | 17 +++--------- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 8b9d1512..88e07237 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -35,6 +35,9 @@ namespace xo { virtual void on_symbol(const std::string & symbol_name, exprstatestack * p_stack, rp * p_emit_expr) override; + virtual void on_typedescr(TypeDescr td, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) override; private: /** diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index e4df8cea..f1ce7669 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -375,6 +375,54 @@ namespace xo { return; } } + + void + define_xs::on_typedescr(TypeDescr td, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + switch (this->exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + /* unreachable */ + assert(false); + return; + + case exprstatetype::def_0: + case exprstatetype::def_1: + /* NOT IMPLEMENTED (ill-formed program) */ + assert(false); + return; + + case exprstatetype::def_2: + this->exs_type_ = exprstatetype::def_3; + this->cvt_expr_ = ConvertExprAccess::make(td /*dest_type*/, + nullptr /*source_expr*/); + this->def_expr_->assign_rhs(this->cvt_expr_); + //this->def_lhs_td_ = td; + + return; + + case exprstatetype::def_3: + case exprstatetype::def_4: + case exprstatetype::def_5: + /* NOT IMPLEMENTED */ + assert(false); + return; + + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_type: + case exprstatetype::expect_symbol: + case exprstatetype::expr_progress: + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + assert(false); + return; + } + } + } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index f58be6bd..2b4b777e 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -549,7 +549,7 @@ namespace xo { } /*on_symbol*/ void - exprstate::on_typedescr(TypeDescr td, + exprstate::on_typedescr(TypeDescr /*td*/, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { @@ -557,25 +557,14 @@ namespace xo { switch (this->exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: + case exprstatetype::def_0: case exprstatetype::def_1: - /* NOT IMPLEMENTED */ - assert(false); - return; - case exprstatetype::def_2: - this->exs_type_ = exprstatetype::def_3; - this->cvt_expr_ = ConvertExprAccess::make(td /*dest_type*/, - nullptr /*source_expr*/); - this->def_expr_->assign_rhs(this->cvt_expr_); - //this->def_lhs_td_ = td; - - return; - case exprstatetype::def_3: case exprstatetype::def_4: case exprstatetype::def_5: - /* NOT IMPLEMENTED */ + /* unreachable */ assert(false); return; From a3b8f778f28a458417a199b0c8a8d3ce34b262be Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 13:32:02 -0400 Subject: [PATCH 1333/2524] xo-reader: refactor: move on_colon() for def-expr to define_xs.cpp --- include/xo/reader/define_xs.hpp | 1 + src/reader/define_xs.cpp | 24 ++++++++++++++++++++++++ src/reader/exprstate.cpp | 10 ++-------- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 88e07237..d3ca7b39 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -38,6 +38,7 @@ namespace xo { virtual void on_typedescr(TypeDescr td, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) override; + virtual void on_colon(exprstatestack * p_stack) override; private: /** diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index f1ce7669..7c0ed3bd 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -423,6 +423,30 @@ namespace xo { } } + void + define_xs::on_colon(exprstatestack * p_stack) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_colon"; + + /* lots of illegal states */ + if (!this->admits_colon()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected colon for parsing state", + xtag("state", *this))); + } + + if (this->exs_type_ == exprstatetype::def_1) { + this->exs_type_ = exprstatetype::def_2; + + p_stack->push_exprstate(exprstate::expect_type()); + } else { + assert(false); + } + } + } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 2b4b777e..d4079646 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -595,7 +595,7 @@ namespace xo { } void - exprstate::on_colon(exprstatestack * p_stack) { + exprstate::on_colon(exprstatestack * /*p_stack*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -609,13 +609,7 @@ namespace xo { xtag("state", *this))); } - if (this->exs_type_ == exprstatetype::def_1) { - this->exs_type_ = exprstatetype::def_2; - - p_stack->push_exprstate(exprstate::expect_type()); - } else { - assert(false); - } + assert(false); } void From 149536b182f86086ac36dec0b24ca801714d86df Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 14:16:12 -0400 Subject: [PATCH 1334/2524] xo-reader: refactor: mv on_semicolon for def-expr to define_xs --- include/xo/reader/define_xs.hpp | 2 ++ src/reader/define_xs.cpp | 28 ++++++++++++++++++++++++++++ src/reader/exprstate.cpp | 8 -------- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index d3ca7b39..b83ada71 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -39,6 +39,8 @@ namespace xo { exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) override; virtual void on_colon(exprstatestack * p_stack) override; + virtual void on_semicolon(exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; private: /** diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 7c0ed3bd..d5513bf2 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -447,6 +447,34 @@ namespace xo { } } + void + define_xs::on_semicolon(exprstatestack * p_stack, + rp * p_emit_expr) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_semicolon"; + + if (!this->admits_semicolon()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected semicolon for parsing state", + xtag("state", *this))); + } + + if (this->exs_type_ == exprstatetype::def_5) { + rp expr = this->def_expr_; + + std::unique_ptr self = p_stack->pop_exprstate(); + + p_stack->top_exprstate().on_expr(expr, + p_stack, + p_emit_expr); + } else { + assert(false); + } + } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index d4079646..77a376c3 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -652,14 +652,6 @@ namespace xo { */ p_stack->top_exprstate().on_semicolon(p_stack, p_emit_expr); - } else if (this->exs_type_ == exprstatetype::def_5) { - rp expr = this->def_expr_; - - std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ - - p_stack->top_exprstate().on_expr(expr, - p_stack, - p_emit_expr); } else { assert(false); } From 5c14ff988469acf616038c0781b8e62c2f38db10 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 14:22:48 -0400 Subject: [PATCH 1335/2524] xo-reader: refactor: mv on_singleassign def-expr -> define_xs --- include/xo/reader/define_xs.hpp | 1 + src/reader/define_xs.cpp | 26 ++++++++++++++++++++++++++ src/reader/exprstate.cpp | 12 ++---------- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index b83ada71..e32d47a8 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -41,6 +41,7 @@ namespace xo { virtual void on_colon(exprstatestack * p_stack) override; virtual void on_semicolon(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; + virtual void on_singleassign(exprstatestack * p_stack) override; private: /** diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index d5513bf2..df468503 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -475,6 +475,32 @@ namespace xo { assert(false); } } + + void + define_xs::on_singleassign(exprstatestack * p_stack) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_singleassign"; + + if (!this->admits_singleassign()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected equals for parsing state", + xtag("state", *this))); + } + + if ((this->exs_type_ == exprstatetype::def_1) + || (this->exs_type_ == exprstatetype::def_3)) + { + this->exs_type_ = exprstatetype::def_4; + + p_stack->push_exprstate(exprstate::expect_rhs_expression()); + } else { + assert(false); + } + } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 77a376c3..a0e9b0c7 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -658,7 +658,7 @@ namespace xo { } void - exprstate::on_singleassign(exprstatestack * p_stack) { + exprstate::on_singleassign(exprstatestack * /*p_stack*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -671,15 +671,7 @@ namespace xo { xtag("state", *this))); } - if ((this->exs_type_ == exprstatetype::def_1) - || (this->exs_type_ == exprstatetype::def_3)) - { - this->exs_type_ = exprstatetype::def_4; - - p_stack->push_exprstate(exprstate::expect_rhs_expression()); - } else { - assert(false); - } + assert(false); } void From 1a6908043b60b97807ea43a37c9b41e4331734cf Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 14:27:01 -0400 Subject: [PATCH 1336/2524] xo-reader: refactor: mv def-expr on_leftparen (noop) -> define_xs --- include/xo/reader/define_xs.hpp | 2 ++ src/reader/define_xs.cpp | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index e32d47a8..f1e0e2c7 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -42,6 +42,8 @@ namespace xo { virtual void on_semicolon(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; virtual void on_singleassign(exprstatestack * p_stack) override; + virtual void on_leftparen(exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; private: /** diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index df468503..4c5b3cb4 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -501,6 +501,25 @@ namespace xo { assert(false); } } + + void + define_xs::on_leftparen(exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_leftparen"; + + if (!this->admits_leftparen()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected leftparen '(' for parsing state", + xtag("state", *this))); + } + + assert(false); /* inserting this during refactor...? */ + } } /*namespace scm*/ } /*namespace xo*/ From 3a0a455b2c711e6a6d7436593691c03e3cff0019 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 14:37:56 -0400 Subject: [PATCH 1337/2524] xo-reader: refactor: mv defexpr on_rightparen(), on_f64() -> define_xs --- include/xo/reader/define_xs.hpp | 5 +++++ include/xo/reader/exprstate.hpp | 1 + src/reader/define_xs.cpp | 39 +++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index f1e0e2c7..de1fb44d 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -44,6 +44,11 @@ namespace xo { virtual void on_singleassign(exprstatestack * p_stack) override; virtual void on_leftparen(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; + virtual void on_rightparen(exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; + virtual void on_f64(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; private: /** diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 461ecddf..75a955ce 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -128,6 +128,7 @@ namespace xo { * forward instructions to parent parser **/ void on_input(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); + /** update exprstate in response to a successfully-parsed subexpression **/ virtual void on_expr(ref::brw expr, exprstatestack * p_stack, diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 4c5b3cb4..f5859c23 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -520,6 +520,45 @@ namespace xo { assert(false); /* inserting this during refactor...? */ } + + void + define_xs::on_rightparen(exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_rightparen"; + + if (!this->admits_rightparen()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected rightparen ')' for parsing state", + xtag("state", *this))); + } + + assert(false); /* inserting this during refactor..? */ + } + + void + define_xs::on_f64(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_f64"; + + if (!this->admits_f64()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected floating-point literal for parsing state", + xtag("state", *this))); + } + + assert(false); + } } /*namespace scm*/ } /*namespace xo*/ From fccff550011b9645573e1cdb48c000e881d65e23 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 15:11:50 -0400 Subject: [PATCH 1338/2524] xo-reader: refactor: splitoff defexpr state machine from exprstate --- include/xo/reader/define_xs.hpp | 17 ++ include/xo/reader/exprstate.hpp | 5 + include/xo/reader/parser.hpp | 11 ++ src/reader/define_xs.cpp | 311 +++++++++++--------------------- src/reader/exprstate.cpp | 133 +++----------- utest/parser.test.cpp | 45 +++-- 6 files changed, 203 insertions(+), 319 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index de1fb44d..0f872007 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -10,6 +10,19 @@ namespace xo { namespace scm { + enum class defexprstatetype { + invalid = -1, + + def_0, + def_1, + def_2, + def_3, + def_4, + def_5, + + n_defexprstatetype, + }; + /** @class define_xs * @brief state to provide parsing of a define-expression **/ @@ -18,8 +31,11 @@ namespace xo { define_xs(rp def_expr); virtual ~define_xs() = default; + static const define_xs * from(const exprstate * x) { return dynamic_cast(x); } static std::unique_ptr def_0(rp def_expr); + defexprstatetype defxs_type() const { return defxs_type_; } + virtual bool admits_definition() const override; virtual bool admits_symbol() const override; virtual bool admits_colon() const override; @@ -71,6 +87,7 @@ namespace xo { * (done): definition complete, pop exprstate from stack * **/ + defexprstatetype defxs_type_; }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 75a955ce..ea37b934 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -20,12 +20,15 @@ namespace xo { /** toplevel of some translation unit **/ expect_toplevel_expression_sequence, + defexpr, +#ifdef OBSOLETE def_0, def_1, def_2, def_3, def_4, def_5, +#endif /* lparen_0: look for expression; capture + advance to lparen_1 */ lparen_0, @@ -63,6 +66,8 @@ namespace xo { }; #endif + class define_xs; + /** state associated with a partially-parsed expression. **/ class exprstate { diff --git a/include/xo/reader/parser.hpp b/include/xo/reader/parser.hpp index fb760081..3211fd0c 100644 --- a/include/xo/reader/parser.hpp +++ b/include/xo/reader/parser.hpp @@ -167,6 +167,17 @@ namespace xo { return exprstatetype::invalid; } + exprstate const * i_exstate(std::size_t i) const { + std::size_t z = xs_stack_.size(); + + if (i < z) { + return xs_stack_[i].get(); + } + + /* out of bounds */ + return nullptr; + } + /** true iff parser contains state for an incomplete expression. * For this to be true, parser must have consumed at least one token * since end of last toplevel expression diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index f5859c23..15ff0130 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -10,40 +10,31 @@ namespace xo { } define_xs::define_xs(rp def_expr) - : exprstate(exprstatetype::def_0, + : exprstate(exprstatetype::defexpr, nullptr /*gen_expr*/, - def_expr) + def_expr), + defxs_type_{defexprstatetype::def_0} {} bool define_xs::admits_definition() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - /* unreachable */ - assert(false); - return false; + switch (defxs_type_) { - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: + case defexprstatetype::def_0: + case defexprstatetype::def_1: + case defexprstatetype::def_2: + case defexprstatetype::def_3: + case defexprstatetype::def_4: + case defexprstatetype::def_5: /* note for def_4: * rhs could certainly be a function body that contains * nested defines; but then immediately-enclosing-exprstate * would be a block */ return false; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - case exprstatetype::expr_progress: - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: + case defexprstatetype::invalid: + case defexprstatetype::n_defexprstatetype: /* unreachable */ assert(false); return false; @@ -54,28 +45,18 @@ namespace xo { bool define_xs::admits_symbol() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - /* unreachable */ - assert(false); + switch (defxs_type_) { + + case defexprstatetype::def_0: + case defexprstatetype::def_1: + case defexprstatetype::def_2: + case defexprstatetype::def_3: + case defexprstatetype::def_4: + case defexprstatetype::def_5: return false; - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - return false; - - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - case exprstatetype::expr_progress: - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: + case defexprstatetype::invalid: + case defexprstatetype::n_defexprstatetype: /* unreachable */ assert(false); return false; @@ -86,32 +67,22 @@ namespace xo { bool define_xs::admits_colon() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - /* unreachable */ - assert(false); + switch (defxs_type_) { + + case defexprstatetype::def_0: return false; - case exprstatetype::def_0: - return false; - - case exprstatetype::def_1: + case defexprstatetype::def_1: return true; - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: + case defexprstatetype::def_2: + case defexprstatetype::def_3: + case defexprstatetype::def_4: + case defexprstatetype::def_5: return false; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - case exprstatetype::expr_progress: - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: + case defexprstatetype::invalid: + case defexprstatetype::n_defexprstatetype: /* unreachable */ assert(false); return false; @@ -122,27 +93,19 @@ namespace xo { bool define_xs::admits_semicolon() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - /* unreachable */ - assert(false); + switch (defxs_type_) { + + case defexprstatetype::def_0: + case defexprstatetype::def_1: + case defexprstatetype::def_2: + case defexprstatetype::def_3: + case defexprstatetype::def_4: return false; - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - return false; - case exprstatetype::def_5: + case defexprstatetype::def_5: return true; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - case exprstatetype::expr_progress: - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: + + case defexprstatetype::invalid: + case defexprstatetype::n_defexprstatetype: /* unreachable */ assert(false); return false; @@ -153,11 +116,7 @@ namespace xo { bool define_xs::admits_singleassign() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - /* unreachable */ - assert(false); - return false; + switch (defxs_type_) { /* * def foo = 1 ; @@ -173,31 +132,24 @@ namespace xo { * * note that we skip from def_1 -> def_4 if '=' instead of ':' */ - case exprstatetype::def_0: + case defexprstatetype::def_0: return false; - case exprstatetype::def_1: + case defexprstatetype::def_1: return true; - case exprstatetype::def_2: + case defexprstatetype::def_2: return false; - case exprstatetype::def_3: + case defexprstatetype::def_3: return true; - case exprstatetype::def_4: - case exprstatetype::def_5: + case defexprstatetype::def_4: + case defexprstatetype::def_5: return false; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - case exprstatetype::expr_progress: - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ + case defexprstatetype::invalid: + case defexprstatetype::n_defexprstatetype: /* unreachable */ assert(false); return false; @@ -208,18 +160,14 @@ namespace xo { bool define_xs::admits_leftparen() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - /* unreachable */ - assert(false); - return false; + switch (defxs_type_) { - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: + case defexprstatetype::def_0: + case defexprstatetype::def_1: + case defexprstatetype::def_2: + case defexprstatetype::def_3: + case defexprstatetype::def_4: + case defexprstatetype::def_5: /* input like * def foo : f64 = ( * ^ ^ ^ ^ ^ @@ -233,14 +181,8 @@ namespace xo { */ return false; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_type: - case exprstatetype::expect_symbol: - case exprstatetype::expr_progress: - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: + case defexprstatetype::invalid: + case defexprstatetype::n_defexprstatetype: /* unreachable */ assert(false); return false; @@ -251,28 +193,18 @@ namespace xo { bool define_xs::admits_rightparen() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - /* unreachable */ - assert(false); + switch (defxs_type_) { + + case defexprstatetype::def_0: + case defexprstatetype::def_1: + case defexprstatetype::def_2: + case defexprstatetype::def_3: + case defexprstatetype::def_4: + case defexprstatetype::def_5: return false; - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - return false; - - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_type: - case exprstatetype::expect_symbol: - case exprstatetype::expr_progress: - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: + case defexprstatetype::invalid: + case defexprstatetype::n_defexprstatetype: /* unreachable */ assert(false); return false; @@ -286,19 +218,16 @@ namespace xo { exprstatestack * /* p_stack */, rp * /* p_emit_expr */) { - switch (this->exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - assert(false); - return; + switch (this->defxs_type_) { - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: + case defexprstatetype::def_0: + case defexprstatetype::def_1: + case defexprstatetype::def_2: + case defexprstatetype::def_3: /* NOT IMPLEMENTED */ assert(false); return; - case exprstatetype::def_4: { + case defexprstatetype::def_4: { /* have all the ingredients to create an expression * representing a definition * @@ -315,22 +244,16 @@ namespace xo { rp def_expr = this->def_expr_; - this->exs_type_ = exprstatetype::def_5; + this->defxs_type_ = defexprstatetype::def_5; return; } - case exprstatetype::def_5: + case defexprstatetype::def_5: assert(false); return; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_type: - case exprstatetype::expect_symbol: - case exprstatetype::expr_progress: - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: + case defexprstatetype::invalid: + case defexprstatetype::n_defexprstatetype: /* unreachable */ assert(false); return; @@ -342,34 +265,24 @@ namespace xo { exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { - switch (this->exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - /* unreachable */ - assert(false); - return; - case exprstatetype::def_0: - this->exs_type_ = exprstatetype::def_1; + switch (this->defxs_type_) { + case defexprstatetype::def_0: + this->defxs_type_ = defexprstatetype::def_1; this->def_expr_->assign_lhs_name(symbol_name); //this->def_lhs_symbol_ = symbol_name; return; - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: + case defexprstatetype::def_1: + case defexprstatetype::def_2: + case defexprstatetype::def_3: + case defexprstatetype::def_4: + case defexprstatetype::def_5: /* NOT IMPLEMENTED */ assert(false); return; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_type: - case exprstatetype::expect_symbol: - case exprstatetype::expr_progress: - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: + case defexprstatetype::invalid: + case defexprstatetype::n_defexprstatetype: /* unreachable */ assert(false); return; @@ -381,20 +294,16 @@ namespace xo { exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { - switch (this->exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - /* unreachable */ - assert(false); - return; + switch (this->defxs_type_) { - case exprstatetype::def_0: - case exprstatetype::def_1: + case defexprstatetype::def_0: + case defexprstatetype::def_1: /* NOT IMPLEMENTED (ill-formed program) */ assert(false); return; - case exprstatetype::def_2: - this->exs_type_ = exprstatetype::def_3; + case defexprstatetype::def_2: + this->defxs_type_ = defexprstatetype::def_3; this->cvt_expr_ = ConvertExprAccess::make(td /*dest_type*/, nullptr /*source_expr*/); this->def_expr_->assign_rhs(this->cvt_expr_); @@ -402,21 +311,15 @@ namespace xo { return; - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: + case defexprstatetype::def_3: + case defexprstatetype::def_4: + case defexprstatetype::def_5: /* NOT IMPLEMENTED */ assert(false); return; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_type: - case exprstatetype::expect_symbol: - case exprstatetype::expr_progress: - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: + case defexprstatetype::invalid: + case defexprstatetype::n_defexprstatetype: /* unreachable */ assert(false); return; @@ -438,8 +341,8 @@ namespace xo { xtag("state", *this))); } - if (this->exs_type_ == exprstatetype::def_1) { - this->exs_type_ = exprstatetype::def_2; + if (this->defxs_type_ == defexprstatetype::def_1) { + this->defxs_type_ = defexprstatetype::def_2; p_stack->push_exprstate(exprstate::expect_type()); } else { @@ -463,7 +366,7 @@ namespace xo { xtag("state", *this))); } - if (this->exs_type_ == exprstatetype::def_5) { + if (this->defxs_type_ == defexprstatetype::def_5) { rp expr = this->def_expr_; std::unique_ptr self = p_stack->pop_exprstate(); @@ -491,10 +394,10 @@ namespace xo { xtag("state", *this))); } - if ((this->exs_type_ == exprstatetype::def_1) - || (this->exs_type_ == exprstatetype::def_3)) + if ((this->defxs_type_ == defexprstatetype::def_1) + || (this->defxs_type_ == defexprstatetype::def_3)) { - this->exs_type_ = exprstatetype::def_4; + this->defxs_type_ = defexprstatetype::def_4; p_stack->push_exprstate(exprstate::expect_rhs_expression()); } else { diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index a0e9b0c7..c61ecc60 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -20,18 +20,8 @@ namespace xo { return "?invalid"; case exprstatetype::expect_toplevel_expression_sequence: return "expect_toplevel_expression_sequence"; - case exprstatetype::def_0: - return "def_0"; - case exprstatetype::def_1: - return "def_1"; - case exprstatetype::def_2: - return "def_2"; - case exprstatetype::def_3: - return "def_3"; - case exprstatetype::def_4: - return "def_4"; - case exprstatetype::def_5: - return "def_5"; + case exprstatetype::defexpr: + return "defexpr"; case exprstatetype::lparen_0: return "lparen_0"; case exprstatetype::lparen_1: @@ -57,12 +47,7 @@ namespace xo { case exprstatetype::expect_toplevel_expression_sequence: return true; - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: + case exprstatetype::defexpr: /* unreachable */ assert(false); return false; @@ -90,12 +75,7 @@ namespace xo { case exprstatetype::expect_toplevel_expression_sequence: return false; - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: + case exprstatetype::defexpr: /* unreachable */ assert(false); return false; @@ -129,16 +109,12 @@ namespace xo { exprstate::admits_colon() const { switch (exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::def_0: + + case exprstatetype::defexpr: + /* unreachable -- redirects to define_xs::admits_colon() */ + assert(false); return false; - case exprstatetype::def_1: - return true; - - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: case exprstatetype::lparen_0: case exprstatetype::lparen_1: case exprstatetype::expect_rhs_expression: @@ -165,14 +141,7 @@ namespace xo { exprstate::admits_semicolon() const { switch (exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - return false; - case exprstatetype::def_5: - return true; + case exprstatetype::defexpr: case exprstatetype::lparen_0: case exprstatetype::lparen_1: case exprstatetype::expect_rhs_expression: @@ -208,21 +177,11 @@ namespace xo { * * note that we skip from def_1 -> def_4 if '=' instead of ':' */ - case exprstatetype::def_0: + case exprstatetype::defexpr: + /* unreachable - redirects to define_xs */ + assert(false); return false; - case exprstatetype::def_1: - return true; - - case exprstatetype::def_2: - return false; - - case exprstatetype::def_3: - return true; - - case exprstatetype::def_4: - case exprstatetype::def_5: - case exprstatetype::lparen_0: case exprstatetype::lparen_1: case exprstatetype::expect_rhs_expression: @@ -249,12 +208,10 @@ namespace xo { exprstate::admits_f64() const { switch (exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: + + case exprstatetype::defexpr: + /* unreachable - redirects to define_xs */ + assert(false); return false; case exprstatetype::lparen_0: @@ -297,13 +254,8 @@ namespace xo { */ return false; - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - /* unreachable */ + case exprstatetype::defexpr: + /* unreachable - redirects to define_xs */ assert(false); return false; @@ -343,13 +295,8 @@ namespace xo { case exprstatetype::expect_toplevel_expression_sequence: return false; - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - /* unreachable */ + case exprstatetype::defexpr: + /* unreachable - redirects to define_xs */ assert(false); return false; @@ -442,13 +389,8 @@ namespace xo { xtag("symbol", tk))); break; - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - /* unreachable */ + case exprstatetype::defexpr: + /* unreachable - redirects to define_xs */ assert(false); return; @@ -558,13 +500,8 @@ namespace xo { switch (this->exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - /* unreachable */ + case exprstatetype::defexpr: + /* unreachable - redirects to define_xs */ assert(false); return; @@ -728,7 +665,7 @@ namespace xo { /* right paren confirms stack expression */ rp expr = this->gen_expr_; - std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + std::unique_ptr self = p_stack->pop_exprstate(); if (p_stack->empty()) { throw std::runtime_error(tostr(self_name, @@ -744,7 +681,7 @@ namespace xo { } else if (this->exs_type_ == exprstatetype::lparen_1) { rp expr = this->gen_expr_; - std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + std::unique_ptr self = p_stack->pop_exprstate(); p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); } @@ -891,13 +828,8 @@ namespace xo { *p_emit_expr = expr.promote(); return; - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - /* unreachable. see define_xs::on_expr() */ + case exprstatetype::defexpr: + /* unreachable. redirects to define_xs::on_expr() */ assert(false); return; @@ -962,13 +894,8 @@ namespace xo { /* NOT IMPLEMENTED */ assert(false); return; - case exprstatetype::def_0: - case exprstatetype::def_1: - case exprstatetype::def_2: - case exprstatetype::def_3: - case exprstatetype::def_4: - case exprstatetype::def_5: - /* unreachable */ + case exprstatetype::defexpr: + /* unreachable - redirects to define_xs */ assert(false); return; diff --git a/utest/parser.test.cpp b/utest/parser.test.cpp index 58910bda..8611a40a 100644 --- a/utest/parser.test.cpp +++ b/utest/parser.test.cpp @@ -4,12 +4,15 @@ */ #include "xo/reader/parser.hpp" +#include "xo/reader/define_xs.hpp" #include namespace xo { using parser_type = xo::scm::parser; using token_type = parser_type::token_type; using xo::scm::exprstatetype; + using xo::scm::define_xs; + using xo::scm::defexprstatetype; using std::cerr; using std::endl; @@ -45,8 +48,11 @@ namespace xo { CHECK(parser.stack_size() == 3); if (parser.stack_size() > 0) CHECK(parser.i_exstype(0) == exprstatetype::expect_symbol); - if (parser.stack_size() > 1) - CHECK(parser.i_exstype(1) == exprstatetype::def_0); + if (parser.stack_size() > 1) { + CHECK(parser.i_exstype(1) == exprstatetype::defexpr); + REQUIRE(define_xs::from(parser.i_exstate(1)) != nullptr); + CHECK(define_xs::from(parser.i_exstate(1))->defxs_type() == defexprstatetype::def_0); + } if (parser.stack_size() > 2) CHECK(parser.i_exstype(2) == exprstatetype::expect_toplevel_expression_sequence); @@ -71,8 +77,11 @@ namespace xo { * def_1 */ CHECK(parser.stack_size() == 2); - if (parser.stack_size() > 0) - CHECK(parser.i_exstype(0) == exprstatetype::def_1); + if (parser.stack_size() > 0) { + CHECK(parser.i_exstype(0) == exprstatetype::defexpr); + REQUIRE(define_xs::from(parser.i_exstate(0)) != nullptr); + CHECK(define_xs::from(parser.i_exstate(0))->defxs_type() == defexprstatetype::def_1); + } if (parser.stack_size() > 1) CHECK(parser.i_exstype(1) == exprstatetype::expect_toplevel_expression_sequence); @@ -104,8 +113,11 @@ namespace xo { CHECK(parser.stack_size() == 3); if (parser.stack_size() > 0) CHECK(parser.i_exstype(0) == exprstatetype::expect_type); - if (parser.stack_size() > 1) - CHECK(parser.i_exstype(1) == exprstatetype::def_2); + if (parser.stack_size() > 1) { + CHECK(parser.i_exstype(1) == exprstatetype::defexpr); + REQUIRE(define_xs::from(parser.i_exstate(1)) != nullptr); + CHECK(define_xs::from(parser.i_exstate(1))->defxs_type() == defexprstatetype::def_2); + } if (parser.stack_size() > 2) CHECK(parser.i_exstype(2) == exprstatetype::expect_toplevel_expression_sequence); @@ -132,8 +144,11 @@ namespace xo { * def_3 */ CHECK(parser.stack_size() == 2); - if (parser.stack_size() > 0) - CHECK(parser.i_exstype(0) == exprstatetype::def_3); + if (parser.stack_size() > 0) { + CHECK(parser.i_exstype(0) == exprstatetype::defexpr); + REQUIRE(define_xs::from(parser.i_exstate(0)) != nullptr); + CHECK(define_xs::from(parser.i_exstate(0))->defxs_type() == defexprstatetype::def_3); + } if (parser.stack_size() > 1) CHECK(parser.i_exstype(1) == exprstatetype::expect_toplevel_expression_sequence); @@ -176,8 +191,11 @@ namespace xo { CHECK(parser.stack_size() == 3); if (parser.stack_size() > 0) CHECK(parser.i_exstype(0) == exprstatetype::expect_rhs_expression); - if (parser.stack_size() > 1) - CHECK(parser.i_exstype(1) == exprstatetype::def_4); + if (parser.stack_size() > 1) { + CHECK(parser.i_exstype(1) == exprstatetype::defexpr); + REQUIRE(define_xs::from(parser.i_exstate(1)) != nullptr); + CHECK(define_xs::from(parser.i_exstate(1))->defxs_type() == defexprstatetype::def_4); + } if (parser.stack_size() > 2) CHECK(parser.i_exstype(2) == exprstatetype::expect_toplevel_expression_sequence); @@ -212,8 +230,11 @@ namespace xo { CHECK(parser.i_exstype(0) == exprstatetype::expr_progress); if (parser.stack_size() > 1) CHECK(parser.i_exstype(1) == exprstatetype::expect_rhs_expression); - if (parser.stack_size() > 2) - CHECK(parser.i_exstype(2) == exprstatetype::def_4); + if (parser.stack_size() > 2) { + CHECK(parser.i_exstype(2) == exprstatetype::defexpr); + REQUIRE(define_xs::from(parser.i_exstate(2)) != nullptr); + CHECK(define_xs::from(parser.i_exstate(2))->defxs_type() == defexprstatetype::def_4); + } if (parser.stack_size() > 3) CHECK(parser.i_exstype(3) == exprstatetype::expect_toplevel_expression_sequence); From cbd411bbf82be627a7e714c18f55df874ac1e33a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 15:15:07 -0400 Subject: [PATCH 1339/2524] xo-reader: refactor: mv exprstate.cvt_expr -> define_xs --- include/xo/reader/define_xs.hpp | 9 +++++++++ include/xo/reader/exprstate.hpp | 13 ------------- src/reader/define_xs.cpp | 11 +++++++++++ src/reader/exprstate.cpp | 2 -- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 0f872007..6e15c21f 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -27,6 +27,9 @@ namespace xo { * @brief state to provide parsing of a define-expression **/ class define_xs : public exprstate { + public: + using ConvertExprAccess = xo::ast::ConvertExprAccess; + public: define_xs(rp def_expr); virtual ~define_xs() = default; @@ -66,6 +69,8 @@ namespace xo { exprstatestack * p_stack, rp * /*p_emit_expr*/) override; + virtual void print(std::ostream & os) const override; + private: /** * def foo : f64 = 1 ; @@ -88,6 +93,10 @@ namespace xo { * **/ defexprstatetype defxs_type_; + /** scafford a convert-expression here. + * May be nested within a def_expr + **/ + rp cvt_expr_; }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index ea37b934..0d051292 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -21,14 +21,6 @@ namespace xo { expect_toplevel_expression_sequence, defexpr, -#ifdef OBSOLETE - def_0, - def_1, - def_2, - def_3, - def_4, - def_5, -#endif /* lparen_0: look for expression; capture + advance to lparen_1 */ lparen_0, @@ -74,7 +66,6 @@ namespace xo { public: using Expression = xo::ast::Expression; using DefineExprAccess = xo::ast::DefineExprAccess; - using ConvertExprAccess = xo::ast::ConvertExprAccess; using exprtype = xo::ast::exprtype; using token_type = token; using TypeDescr = xo::reflect::TypeDescr; @@ -173,10 +164,6 @@ namespace xo { rp gen_expr_; /** scaffold a define-expression here **/ rp def_expr_; - /** scafford a convert-expression here. - * May be nested within a def_expr - **/ - rp cvt_expr_; }; /*exprstate*/ inline std::ostream & diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 15ff0130..932d011c 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -462,6 +462,17 @@ namespace xo { assert(false); } + + void + define_xs::print(std::ostream & os) const { + os << ""; + } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index c61ecc60..5b15ef42 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -930,8 +930,6 @@ namespace xo { << xtag("type", exs_type_); if (def_expr_) os << xtag("def_expr", def_expr_); - if (cvt_expr_) - os << xtag("cvt_expr", cvt_expr_); os << ">"; } From 90a921fa4e366cee0ec0f1426627111f8f255ba0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 15:19:51 -0400 Subject: [PATCH 1340/2524] xo-reader: refactor: mv exprstate.def_expr -> define_xs --- include/xo/reader/define_xs.hpp | 7 ++++++- include/xo/reader/exprstate.hpp | 23 ++++++++--------------- src/reader/define_xs.cpp | 10 +++++----- src/reader/exprstate.cpp | 5 +---- 4 files changed, 20 insertions(+), 25 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 6e15c21f..3a740a32 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -5,6 +5,8 @@ #pragma once +#include "xo/expression/DefineExpr.hpp" +#include "xo/expression/ConvertExpr.hpp" #include "exprstate.hpp" //#include @@ -28,6 +30,7 @@ namespace xo { **/ class define_xs : public exprstate { public: + using DefineExprAccess = xo::ast::DefineExprAccess; using ConvertExprAccess = xo::ast::ConvertExprAccess; public: @@ -35,7 +38,7 @@ namespace xo { virtual ~define_xs() = default; static const define_xs * from(const exprstate * x) { return dynamic_cast(x); } - static std::unique_ptr def_0(rp def_expr); + static std::unique_ptr def_0(); defexprstatetype defxs_type() const { return defxs_type_; } @@ -93,6 +96,8 @@ namespace xo { * **/ defexprstatetype defxs_type_; + /** scaffold a define-expression here **/ + rp def_expr_; /** scafford a convert-expression here. * May be nested within a def_expr **/ diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 0d051292..ae1a0bf0 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -6,8 +6,6 @@ #pragma once #include "xo/expression/Expression.hpp" -#include "xo/expression/DefineExpr.hpp" -#include "xo/expression/ConvertExpr.hpp" #include "xo/tokenizer/token.hpp" #include //#include @@ -65,7 +63,6 @@ namespace xo { class exprstate { public: using Expression = xo::ast::Expression; - using DefineExprAccess = xo::ast::DefineExprAccess; using exprtype = xo::ast::exprtype; using token_type = token; using TypeDescr = xo::reflect::TypeDescr; @@ -73,30 +70,28 @@ namespace xo { public: exprstate() = default; exprstate(exprstatetype exs_type, - rp candidate_expr, - rp def_expr) + rp candidate_expr) : exs_type_{exs_type}, - gen_expr_{std::move(candidate_expr)}, - def_expr_{std::move(def_expr)} {} + gen_expr_{std::move(candidate_expr)} {} virtual ~exprstate() = default; static std::unique_ptr expect_toplevel_expression_sequence() { - return std::make_unique(exprstate(exprstatetype::expect_toplevel_expression_sequence, nullptr, nullptr)); + return std::make_unique(exprstate(exprstatetype::expect_toplevel_expression_sequence, nullptr)); } static std::unique_ptr expect_rhs_expression() { - return std::make_unique(exprstate(exprstatetype::expect_rhs_expression, nullptr, nullptr)); + return std::make_unique(exprstate(exprstatetype::expect_rhs_expression, nullptr)); } static std::unique_ptr expect_symbol() { - return std::make_unique(exprstate(exprstatetype::expect_symbol, nullptr, nullptr)); + return std::make_unique(exprstate(exprstatetype::expect_symbol, nullptr)); } static std::unique_ptr expect_type() { - return std::make_unique(exprstate(exprstatetype::expect_type, nullptr, nullptr)); + return std::make_unique(exprstate(exprstatetype::expect_type, nullptr)); } static std::unique_ptr make_expr_progress(rp expr) { - return std::make_unique(exprstate(exprstatetype::expr_progress, expr, nullptr)); + return std::make_unique(exprstate(exprstatetype::expr_progress, expr)); } static std::unique_ptr lparen_0() { - return std::make_unique(exprstate(exprstatetype::lparen_0, nullptr, nullptr)); + return std::make_unique(exprstate(exprstatetype::lparen_0, nullptr)); } exprstatetype exs_type() const { return exs_type_; } @@ -162,8 +157,6 @@ namespace xo { /** generic expression **/ rp gen_expr_; - /** scaffold a define-expression here **/ - rp def_expr_; }; /*exprstate*/ inline std::ostream & diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 932d011c..d1f93984 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -5,15 +5,15 @@ namespace xo { namespace scm { std::unique_ptr - define_xs::def_0(rp def_expr) { - return std::make_unique(define_xs(def_expr)); + define_xs::def_0() { + return std::make_unique(define_xs(DefineExprAccess::make_empty())); } define_xs::define_xs(rp def_expr) : exprstate(exprstatetype::defexpr, - nullptr /*gen_expr*/, - def_expr), - defxs_type_{defexprstatetype::def_0} + nullptr /*gen_expr*/), + defxs_type_{defexprstatetype::def_0}, + def_expr_{std::move(def_expr)} {} bool diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 5b15ef42..db98d197 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -346,8 +346,7 @@ namespace xo { xtag("state", *this))); } - p_stack->push_exprstate - (define_xs::def_0(DefineExprAccess::make_empty())); + p_stack->push_exprstate(define_xs::def_0()); /* todo: replace: * expect_symbol_or_function_signature() @@ -928,8 +927,6 @@ namespace xo { exprstate::print(std::ostream & os) const { os << ""; } From 5d3e92d1144f319d403dcc5259a72562362af332 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Aug 2024 16:34:35 -0400 Subject: [PATCH 1341/2524] xo-reader: refactor: mv expr_progress impl -new-> progress_xs.*pp --- include/xo/reader/exprstate.hpp | 4 +- include/xo/reader/progress_xs.hpp | 70 ++++++++++ src/reader/CMakeLists.txt | 3 +- src/reader/exprstate.cpp | 92 ++++--------- src/reader/progress_xs.cpp | 216 ++++++++++++++++++++++++++++++ 5 files changed, 316 insertions(+), 69 deletions(-) create mode 100644 include/xo/reader/progress_xs.hpp create mode 100644 src/reader/progress_xs.cpp diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index ae1a0bf0..82ffe0c2 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -87,9 +87,11 @@ namespace xo { static std::unique_ptr expect_type() { return std::make_unique(exprstate(exprstatetype::expect_type, nullptr)); } +#ifdef RELOCATED static std::unique_ptr make_expr_progress(rp expr) { return std::make_unique(exprstate(exprstatetype::expr_progress, expr)); } +#endif static std::unique_ptr lparen_0() { return std::make_unique(exprstate(exprstatetype::lparen_0, nullptr)); } @@ -135,7 +137,7 @@ namespace xo { /** print human-readable representation on @p os **/ virtual void print(std::ostream & os) const; - protected: + public: virtual void on_def(exprstatestack * p_stack); virtual void on_symbol(const token_type & tk, exprstatestack * p_stack, diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp new file mode 100644 index 00000000..5b2ce28f --- /dev/null +++ b/include/xo/reader/progress_xs.hpp @@ -0,0 +1,70 @@ +/** @file progress_xs.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "exprstate.hpp" +//#include + +namespace xo { + namespace scm { + /** @class progress_xs + * @brief state machine for parsing a schematica runtime-value-expression + **/ + class progress_xs : public exprstate { + public: + progress_xs(rp valex); + virtual ~progress_xs() = default; + + static const progress_xs * from(const exprstate * x) { return dynamic_cast(x); } + + static std::unique_ptr make(rp valex); + + virtual bool admits_definition() const override; + virtual bool admits_symbol() const override; + virtual bool admits_colon() const override; + virtual bool admits_semicolon() const override; + virtual bool admits_singleassign() const override; + virtual bool admits_leftparen() const override; + virtual bool admits_rightparen() const override; + virtual bool admits_f64() const override; + + // virtual void on_f64(..) override + virtual void on_def(exprstatestack * p_stack) override; + + virtual void on_expr(ref::brw expr, + exprstatestack * p_stack, + rp * p_emit_expr) override; + virtual void on_symbol(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; + virtual void on_typedescr(TypeDescr td, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) override; + virtual void on_colon(exprstatestack * p_stack) override; + virtual void on_semicolon(exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; + virtual void on_singleassign(exprstatestack * p_stack) override; + virtual void on_leftparen(exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; + virtual void on_rightparen(exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; + virtual void on_f64(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; + + virtual void print(std::ostream & os) const override; + + private: +#ifdef NOT_YET + /** populate an expression here **/ + rp gen_expr_; +#endif + }; + } /*namespace scm*/ +} /*namespace xo*/ + + +/** end progress_xs.hpp **/ diff --git a/src/reader/CMakeLists.txt b/src/reader/CMakeLists.txt index 8896040e..29bb9155 100644 --- a/src/reader/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -5,7 +5,8 @@ set(SELF_SRCS parser.cpp reader.cpp exprstate.cpp - define_xs.cpp) + define_xs.cpp + progress_xs.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_dependency(${SELF_LIB} xo_expression) diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index db98d197..b463c00b 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -2,6 +2,7 @@ #include "exprstate.hpp" #include "define_xs.hpp" +#include "progress_xs.hpp" //#include "xo/expression/DefineExpr.hpp" #include "xo/expression/Constant.hpp" //#include "xo/expression/ConvertExpr.hpp" @@ -59,6 +60,8 @@ namespace xo { case exprstatetype::expect_type: return false; case exprstatetype::expr_progress: + /* unreachable */ + assert(false); return false; case exprstatetype::invalid: case exprstatetype::n_exprstatetype: @@ -94,6 +97,8 @@ namespace xo { return true; case exprstatetype::expr_progress: + /* unreachable */ + assert(false); return false; case exprstatetype::invalid: @@ -126,6 +131,8 @@ namespace xo { return false; case exprstatetype::expr_progress: + /* unreachable */ + assert(false); return false; case exprstatetype::invalid: @@ -149,6 +156,8 @@ namespace xo { case exprstatetype::expect_type: return false; case exprstatetype::expr_progress: + /* unreachable */ + assert(false); return true; case exprstatetype::invalid: case exprstatetype::n_exprstatetype: @@ -193,6 +202,8 @@ namespace xo { return false; case exprstatetype::expr_progress: + /* unreachable */ + assert(false); return false; case exprstatetype::invalid: @@ -228,6 +239,8 @@ namespace xo { return false; case exprstatetype::expr_progress: + /* unreachable */ + assert(false); return false; case exprstatetype::invalid: @@ -276,7 +289,8 @@ namespace xo { return false; case exprstatetype::expr_progress: - /* todo: will parse as function call */ + /* unreachable */ + assert(false); return false; case exprstatetype::invalid: @@ -318,9 +332,6 @@ namespace xo { return false; case exprstatetype::expr_progress: - /* satisfies expression form */ - return true; - case exprstatetype::invalid: case exprstatetype::n_exprstatetype: /* unreachable */ @@ -475,9 +486,7 @@ namespace xo { } case exprstatetype::expr_progress: - /* illegal input, e.g. - * foo bar - */ + /* unreachable */ assert(false); return; @@ -519,6 +528,7 @@ namespace xo { return; case exprstatetype::expr_progress: + /* unreachable */ assert(false); return; @@ -549,8 +559,8 @@ namespace xo { } void - exprstate::on_semicolon(exprstatestack * p_stack, - rp * p_emit_expr) + exprstate::on_semicolon(exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -564,33 +574,7 @@ namespace xo { xtag("state", *this))); } - if (this->exs_type_ == exprstatetype::expr_progress) { - rp expr = this->gen_expr_; - - std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ - - p_stack->top_exprstate().on_expr(expr, - p_stack, - p_emit_expr); - /* control here on input like: - * (1.234; - * - * a. '(' sets up stack [lparen_0:expect_rhs_expression] - * (see exprstate::on_leftparen()) - * b. 1.234 pushes (in case operators) [lparen_0:expect_rhs_expression:expr_progress] - * (see exprstate::on_f64()) - * c. semicolon completes expr_progress [lparen_0:expect_rhs_expression] - * deliver expresssion to expect_rhs_expression.on_expr() - * (see exprstate::on_expr()) - * d. expr_rhs_expression forwards expression to [lparen_0] - * e. lparen_0 advances to [lparen_1] - * f. now deliver semicolon; [lparen_1] rejects - */ - - p_stack->top_exprstate().on_semicolon(p_stack, p_emit_expr); - } else { - assert(false); - } + assert(false); } void @@ -650,33 +634,8 @@ namespace xo { } if (this->exs_type_ == exprstatetype::expr_progress) { - /* stack may be something like: - * - * lparen_0 - * expect_rhs_expression - * expr_progress - * <-- rightparen - * - * 1. rightparen completes expression-in-progress - * 2. rightparen must then match innermost waiting lparen_0 - */ - - /* right paren confirms stack expression */ - rp expr = this->gen_expr_; - - std::unique_ptr self = p_stack->pop_exprstate(); - - if (p_stack->empty()) { - throw std::runtime_error(tostr(self_name, - ": expected non-empty parsing stack")); - } - - log && log(xtag("stack", p_stack)); - - p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); - - /* now deliver rightparen */ - p_stack->top_exprstate().on_rightparen(p_stack, p_emit_expr); + /* unreachable -- see progress_xs::on_rightparen() */ + assert(false); } else if (this->exs_type_ == exprstatetype::lparen_1) { rp expr = this->gen_expr_; @@ -709,7 +668,7 @@ namespace xo { * \---tk---/ */ p_stack->push_exprstate - (exprstate::make_expr_progress + (progress_xs::make (Constant::make(tk.f64_value()))); } else { assert(false); @@ -834,7 +793,7 @@ namespace xo { case exprstatetype::lparen_0: { this->exs_type_ = exprstatetype::lparen_1; /* wants on_rightparen */ - p_stack->push_exprstate(exprstate::make_expr_progress(expr.promote())); + p_stack->push_exprstate(progress_xs::make(expr.promote())); return; } @@ -865,8 +824,7 @@ namespace xo { assert(false); return; case exprstatetype::expr_progress: - /* consecutive expressions isn't legal - */ + /* unreachable */ assert(false); return; case exprstatetype::invalid: diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp new file mode 100644 index 00000000..a9701901 --- /dev/null +++ b/src/reader/progress_xs.cpp @@ -0,0 +1,216 @@ +/* @file progress_xs.cpp */ + +#include "progress_xs.hpp" + +namespace xo { + namespace scm { + std::unique_ptr + progress_xs::make(rp valex) { + return std::make_unique(progress_xs(std::move(valex))); + } + + progress_xs::progress_xs(rp valex) + : exprstate(exprstatetype::expr_progress, + std::move(valex)) + {} + + bool + progress_xs::admits_definition() const { return false; } + + bool + progress_xs::admits_symbol() const { return false; } + + bool + progress_xs::admits_colon() const { return false; } + + bool + progress_xs::admits_semicolon() const { return true; } + + bool + progress_xs::admits_singleassign() const { return false; } + + /* todo: will parse as function call */ + bool + progress_xs::admits_leftparen() const { return false; } + + bool + progress_xs::admits_rightparen() const { + /* satisfies expression form */ + return true; + } + + bool + progress_xs::admits_f64() const { return false; } + + void + progress_xs::on_def(exprstatestack * /*p_stack*/) { + constexpr const char * self_name = "progress_xs::on_def"; + + /* nothing here - admits_definition unconditionally false */ + throw std::runtime_error(tostr(self_name, + ": unexpected keyword 'def' for parsing state", + xtag("state", *this))); + } + + void + progress_xs::on_expr(ref::brw /*expr*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + /* consecutive expressions isn't legal */ + assert(false); + } + + void + progress_xs::on_symbol(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + /* illegal input, e.g. + * foo bar + */ + assert(false); + } + + void + progress_xs::on_typedescr(TypeDescr /*td*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + /* unreachable */ + assert(false); + } + + void + progress_xs::on_colon(exprstatestack * /*p_stack*/) { + constexpr const char * self_name = "progress_xs::on_colon"; + + throw std::runtime_error(tostr(self_name, + ": unexpected colon for parsing state", + xtag("state", *this))); + } + + void + progress_xs::on_semicolon(exprstatestack * p_stack, + rp * p_emit_expr) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + rp expr = this->gen_expr_; + + std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + + p_stack->top_exprstate().on_expr(expr, + p_stack, + p_emit_expr); + /* control here on input like: + * (1.234; + * + * a. '(' sets up stack [lparen_0:expect_rhs_expression] + * (see exprstate::on_leftparen()) + * b. 1.234 pushes (in case operators) [lparen_0:expect_rhs_expression:expr_progress] + * (see exprstate::on_f64()) + * c. semicolon completes expr_progress [lparen_0:expect_rhs_expression] + * deliver expresssion to expect_rhs_expression.on_expr() + * (see exprstate::on_expr()) + * d. expr_rhs_expression forwards expression to [lparen_0] + * e. lparen_0 advances to [lparen_1] + * f. now deliver semicolon; [lparen_1] rejects + */ + + p_stack->top_exprstate().on_semicolon(p_stack, p_emit_expr); + } + + void + progress_xs::on_singleassign(exprstatestack * /*p_stack*/) { + constexpr const char * self_name = "progress_xs::on_singleassign"; + + throw std::runtime_error(tostr(self_name, + ": unexpected equals for parsing state", + xtag("state", *this))); + } + + void + progress_xs::on_leftparen(exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_leftparen"; + + throw std::runtime_error(tostr(self_name, + ": unexpected leftparen '(' for parsing state", + xtag("state", *this))); + } + + void + progress_xs::on_rightparen(exprstatestack * p_stack, + rp * p_emit_expr) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "progress_xs::on_rightparen"; + + /* stack may be something like: + * + * lparen_0 + * expect_rhs_expression + * expr_progress + * <-- rightparen + * + * 1. rightparen completes expression-in-progress + * 2. rightparen must then match innermost waiting lparen_0 + */ + + /* right paren confirms stack expression */ + rp expr = this->gen_expr_; + + std::unique_ptr self = p_stack->pop_exprstate(); + + if (p_stack->empty()) { + throw std::runtime_error(tostr(self_name, + ": expected non-empty parsing stack")); + } + + log && log(xtag("stack", p_stack)); + + p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); + + /* now deliver rightparen */ + p_stack->top_exprstate().on_rightparen(p_stack, p_emit_expr); + + } + + void + progress_xs::on_f64(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "progress_xs::on_f64"; + + throw std::runtime_error(tostr(self_name, + ": unexpected floating-point literal for parsing state", + xtag("state", *this))); + } + + void + progress_xs::print(std::ostream & os) const { + os << ""; + } + + + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end progress_xs.cpp */ From ccfd0660d5e30b99760dad93adf03b1150f18bc1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 11:41:40 -0400 Subject: [PATCH 1342/2524] xo-reader: wip: + paren_xs impl [not used] --- include/xo/reader/paren_xs.hpp | 88 +++++++++++++ src/reader/CMakeLists.txt | 3 +- src/reader/paren_xs.cpp | 222 +++++++++++++++++++++++++++++++++ 3 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 include/xo/reader/paren_xs.hpp create mode 100644 src/reader/paren_xs.cpp diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp new file mode 100644 index 00000000..a8211d96 --- /dev/null +++ b/include/xo/reader/paren_xs.hpp @@ -0,0 +1,88 @@ +/** @file paren_xs.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "exprstate.hpp" +//#include + +namespace xo { + namespace scm { + enum class parenexprstatetype { + invalid = -1, + + lparen_0, + lparen_1, + + n_parenexprstatetype, + }; + + /** @class paren_xs + * @brief state machine for handling parentheses in expressions + **/ + class paren_xs : public exprstate { + public: + //paren_xs(rp valex); + virtual ~paren_xs() = default; + + static const paren_xs * from(const exprstate * x) { return dynamic_cast(x); } + + //static std::unique_ptr make(); + + virtual bool admits_definition() const override; + virtual bool admits_symbol() const override; + virtual bool admits_colon() const override; + virtual bool admits_semicolon() const override; + virtual bool admits_singleassign() const override; + virtual bool admits_leftparen() const override; + virtual bool admits_rightparen() const override; + virtual bool admits_f64() const override; + + // virtual void on_f64(..) override + virtual void on_def(exprstatestack * p_stack) override; + + virtual void on_expr(ref::brw expr, + exprstatestack * p_stack, + rp * p_emit_expr) override; + virtual void on_symbol(const std::string & symbol, + exprstatestack * p_stack, + rp * p_emit_expr) override; + virtual void on_typedescr(TypeDescr td, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) override; + + virtual void on_symbol(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; + virtual void on_colon(exprstatestack * p_stack) override; + virtual void on_semicolon(exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; + virtual void on_singleassign(exprstatestack * p_stack) override; + virtual void on_leftparen(exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; + virtual void on_rightparen(exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; + virtual void on_f64(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; + + virtual void print(std::ostream & os) const override; + + private: +#ifdef NOT_YET + /** + * ( foo ... ) + * ^ + * | + * lparen_0 + **/ + parenexprstatetype parenxs_type_; +#endif + }; + } /*namespace scm*/ +} /*namespace xo*/ + + +/** end paren_xs.hpp **/ diff --git a/src/reader/CMakeLists.txt b/src/reader/CMakeLists.txt index 29bb9155..4741fa3d 100644 --- a/src/reader/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -6,7 +6,8 @@ set(SELF_SRCS reader.cpp exprstate.cpp define_xs.cpp - progress_xs.cpp) + progress_xs.cpp + paren_xs.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_dependency(${SELF_LIB} xo_expression) diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp new file mode 100644 index 00000000..3fe5e40d --- /dev/null +++ b/src/reader/paren_xs.cpp @@ -0,0 +1,222 @@ +/* @file paren_xs.cpp */ + +#include "paren_xs.hpp" +#include "progress_xs.hpp" + +namespace xo { + namespace scm { + bool + paren_xs::admits_definition() const { return false; } + + bool + paren_xs::admits_symbol() const { return true; } + + bool + paren_xs::admits_colon() const { return false; } + + bool + paren_xs::admits_semicolon() const { return false; } + + bool + paren_xs::admits_singleassign() const { return false; } + + bool + paren_xs::admits_leftparen() const { /*unreachable*/ return false; } + + /** TODO: fixme **/ + bool + paren_xs::admits_rightparen() const { return exprstate::admits_rightparen(); } + + /** TODO: fixme **/ + bool + paren_xs::admits_f64() const { return exprstate::admits_f64(); } + + void + paren_xs::on_def(exprstatestack * /*p_stack*/) { + constexpr const char * c_self_name = "paren_xs::on_def"; + + throw std::runtime_error(tostr(c_self_name, + ": unexpected keyword 'def' for parsing state", + xtag("state", *this))); + } + + void + paren_xs::on_symbol(const token_type & /*tk*/, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("exstype", p_stack->top_exprstate().exs_type())); + + //constexpr const char * self_name = "paren_xs::on_symbol"; + + /* TODO: lparen_0: treat as variable reference */ + + assert(false); + } + + void + paren_xs::on_typedescr(TypeDescr /*td*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + assert(false); + return; + } + + void + paren_xs::on_colon(exprstatestack * /*p_stack*/) + { + constexpr const char * c_self_name = "paren_xs::on_colon"; + + throw std::runtime_error(tostr(c_self_name, + ": unexpected colon for parsing state", + xtag("state", *this))); + } + + void + paren_xs::on_semicolon(exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + constexpr const char * c_self_name = "paren_xs::on_semicolon"; + + throw std::runtime_error(tostr(c_self_name, + ": unexpected semicolon for parsing state", + xtag("state", *this))); + } + + void + paren_xs::on_singleassign(exprstatestack * /*p_stack*/) + { + constexpr const char * c_self_name = "paren_xs::on_singleassign"; + + throw std::runtime_error(tostr(c_self_name, + ": unexpected equals for parsing state", + xtag("state", *this))); + } + + void + paren_xs::on_leftparen(exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + constexpr const char * c_self_name = "paren_xs::on_leftparen"; + + throw std::runtime_error(tostr(c_self_name, + ": unexpected leftparen '(' for parsing state", + xtag("state", *this))); + } + + void + paren_xs::on_rightparen(exprstatestack * p_stack, + rp * p_emit_expr) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "paren_xs::on_rightparen"; + + if (!this->admits_rightparen()) + { + throw std::runtime_error(tostr(self_name, + ": unexpected rightparen ')' for parsing state", + xtag("state", *this))); + } + + if (this->exs_type_ == exprstatetype::lparen_1) { + rp expr = this->gen_expr_; + + std::unique_ptr self = p_stack->pop_exprstate(); + + p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); + } + } + + void + paren_xs::on_f64(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * c_self_name = "paren_xs::on_f64"; + + if (!this->admits_f64()) + { + throw std::runtime_error(tostr(c_self_name, + ": unexpected floating-point literal for parsing state", + xtag("state", *this))); + } + + assert(false); + } + + void + paren_xs::on_expr(ref::brw expr, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("exstype", this->exs_type_), + xtag("expr", expr)); + + switch (this->exs_type_) { + case exprstatetype::lparen_0: { + this->exs_type_ = exprstatetype::lparen_1; /* wants on_rightparen */ + p_stack->push_exprstate(progress_xs::make(expr.promote())); + + return; + } + + case exprstatetype::lparen_1: { + this->gen_expr_ = expr.promote(); + + /* expect immediate incoming call, this time to on_rightparen() */ + return; + } + + default: + /* unreachable */ + assert(false); + return; + } + } /*on_expr*/ + + void + paren_xs::on_symbol(const std::string & /*symbol_name*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + switch(this->exs_type_) { + case exprstatetype::lparen_0: + case exprstatetype::lparen_1: + /* NOT IMPLEMENTED */ + assert(false); + return; + + default: + /* unreachable */ + assert(false); + return; + } + } + + void + paren_xs::print(std::ostream & os) const { + os << ""; + } + + + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end paren_xs.cpp */ From 67e76b5d8cab6f629403597a0d337518fd3c2a37 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 11:42:05 -0400 Subject: [PATCH 1343/2524] xo-reader: nit: fixup progress_xs.print() output --- src/reader/progress_xs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index a9701901..6e1d743a 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -201,7 +201,7 @@ namespace xo { void progress_xs::print(std::ostream & os) const { - os << " Date: Fri, 9 Aug 2024 11:55:22 -0400 Subject: [PATCH 1344/2524] xo-reader: refactor: splitoff paren handline -> paren_xs --- include/xo/reader/exprstate.hpp | 19 +--- include/xo/reader/paren_xs.hpp | 6 +- src/reader/exprstate.cpp | 174 +++++++++++--------------------- src/reader/paren_xs.cpp | 65 +++++++++--- 4 files changed, 116 insertions(+), 148 deletions(-) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 82ffe0c2..fa0b466f 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -20,10 +20,8 @@ namespace xo { defexpr, - /* lparen_0: look for expression; capture + advance to lparen_1 */ - lparen_0, - /* lparen_1: expect rightparen */ - lparen_1, + /* handle parenthesized expression */ + parenexpr, expect_rhs_expression, expect_symbol, @@ -45,17 +43,6 @@ namespace xo { class exprstatestack; -#ifdef NOT_YET - class exprstateaux { - public: - }; - - class lparen_xsa : public exprstateaux { - public: - private: - }; -#endif - class define_xs; /** state associated with a partially-parsed expression. @@ -92,9 +79,11 @@ namespace xo { return std::make_unique(exprstate(exprstatetype::expr_progress, expr)); } #endif +#ifdef RELOCATED static std::unique_ptr lparen_0() { return std::make_unique(exprstate(exprstatetype::lparen_0, nullptr)); } +#endif exprstatetype exs_type() const { return exs_type_; } diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index a8211d96..a6a8eb3e 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -24,12 +24,12 @@ namespace xo { **/ class paren_xs : public exprstate { public: - //paren_xs(rp valex); + paren_xs(); virtual ~paren_xs() = default; static const paren_xs * from(const exprstate * x) { return dynamic_cast(x); } - //static std::unique_ptr make(); + static std::unique_ptr lparen_0(); virtual bool admits_definition() const override; virtual bool admits_symbol() const override; @@ -71,7 +71,6 @@ namespace xo { virtual void print(std::ostream & os) const override; private: -#ifdef NOT_YET /** * ( foo ... ) * ^ @@ -79,7 +78,6 @@ namespace xo { * lparen_0 **/ parenexprstatetype parenxs_type_; -#endif }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index b463c00b..351fbf5a 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -3,6 +3,7 @@ #include "exprstate.hpp" #include "define_xs.hpp" #include "progress_xs.hpp" +#include "paren_xs.hpp" //#include "xo/expression/DefineExpr.hpp" #include "xo/expression/Constant.hpp" //#include "xo/expression/ConvertExpr.hpp" @@ -23,10 +24,8 @@ namespace xo { return "expect_toplevel_expression_sequence"; case exprstatetype::defexpr: return "defexpr"; - case exprstatetype::lparen_0: - return "lparen_0"; - case exprstatetype::lparen_1: - return "lparen_1"; + case exprstatetype::parenexpr: + return "parenexpr"; case exprstatetype::expect_rhs_expression: return "expect_rhs_expression"; case exprstatetype::expect_symbol: @@ -49,11 +48,10 @@ namespace xo { return true; case exprstatetype::defexpr: + case exprstatetype::parenexpr: /* unreachable */ assert(false); return false; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: case exprstatetype::expect_rhs_expression: return false; case exprstatetype::expect_symbol: @@ -79,12 +77,11 @@ namespace xo { return false; case exprstatetype::defexpr: + case exprstatetype::parenexpr: /* unreachable */ assert(false); return false; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: case exprstatetype::expect_rhs_expression: /* treat symbol as variable name */ return true; @@ -116,12 +113,11 @@ namespace xo { case exprstatetype::expect_toplevel_expression_sequence: case exprstatetype::defexpr: - /* unreachable -- redirects to define_xs::admits_colon() */ + case exprstatetype::parenexpr: + /* unreachable -- redirects to define_xs::admits_colon() etc */ assert(false); return false; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: case exprstatetype::expect_rhs_expression: /* rhs-expressions (or expressions for that matter) * may not begin with a colon @@ -149,8 +145,7 @@ namespace xo { switch (exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: case exprstatetype::defexpr: - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: + case exprstatetype::parenexpr: case exprstatetype::expect_rhs_expression: case exprstatetype::expect_symbol: case exprstatetype::expect_type: @@ -187,12 +182,11 @@ namespace xo { * note that we skip from def_1 -> def_4 if '=' instead of ':' */ case exprstatetype::defexpr: - /* unreachable - redirects to define_xs */ + case exprstatetype::parenexpr: + /* unreachable - redirects to define_xs etrc */ assert(false); return false; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: case exprstatetype::expect_rhs_expression: /* rhs-expressions (or expressions for that matter) * may not begin with singleassign '=' @@ -215,43 +209,6 @@ namespace xo { return false; } - bool - exprstate::admits_f64() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - - case exprstatetype::defexpr: - /* unreachable - redirects to define_xs */ - assert(false); - return false; - - case exprstatetype::lparen_0: - return true; - - case exprstatetype::lparen_1: - return false; - - case exprstatetype::expect_rhs_expression: - return true; - - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - return false; - - case exprstatetype::expr_progress: - /* unreachable */ - assert(false); - return false; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - return false; - } - - return false; - } - bool exprstate::admits_leftparen() const { switch (exs_type_) { @@ -268,16 +225,11 @@ namespace xo { return false; case exprstatetype::defexpr: + case exprstatetype::parenexpr: /* unreachable - redirects to define_xs */ assert(false); return false; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - /* unreachable */ - assert(false); - return false; - case exprstatetype::expect_rhs_expression: /* can always begin non-toplevel expression with '(' */ return true; @@ -310,18 +262,11 @@ namespace xo { return false; case exprstatetype::defexpr: + case exprstatetype::parenexpr: /* unreachable - redirects to define_xs */ assert(false); return false; - case exprstatetype::lparen_0: - /* unreachable -- will have pushed expect_rhs_expression */ - assert(false); - return false; - - case exprstatetype::lparen_1: - return true; - case exprstatetype::expect_rhs_expression: return false; @@ -342,6 +287,38 @@ namespace xo { return false; } + bool + exprstate::admits_f64() const { + switch (exs_type_) { + case exprstatetype::expect_toplevel_expression_sequence: + + case exprstatetype::defexpr: + case exprstatetype::parenexpr: + /* unreachable - redirects to define_xs */ + assert(false); + return false; + + case exprstatetype::expect_rhs_expression: + return true; + + case exprstatetype::expect_symbol: + case exprstatetype::expect_type: + return false; + + case exprstatetype::expr_progress: + /* unreachable */ + assert(false); + return false; + + case exprstatetype::invalid: + case exprstatetype::n_exprstatetype: + /* unreachable */ + return false; + } + + return false; + } + void exprstate::on_def(exprstatestack * p_stack) { constexpr bool c_debug_flag = true; @@ -400,21 +377,11 @@ namespace xo { break; case exprstatetype::defexpr: + case exprstatetype::parenexpr: /* unreachable - redirects to define_xs */ assert(false); return; - case exprstatetype::lparen_0: - /* todo: variable reference */ - assert(false); - break; - - case exprstatetype::lparen_1: - /* unreachable */ - - assert(false); - break; - case exprstatetype::expect_rhs_expression: { /* various possibilities when looking for rhs expression: @@ -503,21 +470,17 @@ namespace xo { exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { - /* returning type description to somethign that wants it */ + /* returning type description to something that wants it */ switch (this->exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: case exprstatetype::defexpr: + case exprstatetype::parenexpr: /* unreachable - redirects to define_xs */ assert(false); return; - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - assert(false); - return; - case exprstatetype::expect_rhs_expression: case exprstatetype::expect_type: case exprstatetype::expect_symbol: @@ -612,14 +575,14 @@ namespace xo { if (this->exs_type_ == exprstatetype::expect_rhs_expression) { /* push lparen_0 to remember to look for subsequent rightparen. */ - p_stack->push_exprstate(exprstate::lparen_0()); + p_stack->push_exprstate(paren_xs::lparen_0()); p_stack->push_exprstate(exprstate::expect_rhs_expression()); } } void - exprstate::on_rightparen(exprstatestack * p_stack, - rp * p_emit_expr) + exprstate::on_rightparen(exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -633,15 +596,10 @@ namespace xo { xtag("state", *this))); } - if (this->exs_type_ == exprstatetype::expr_progress) { - /* unreachable -- see progress_xs::on_rightparen() */ + if (this->exs_type_ == exprstatetype::expr_progress + || this->exs_type_ == exprstatetype::parenexpr) { + /* unreachable -- see progress_xs::on_rightparen() etc */ assert(false); - } else if (this->exs_type_ == exprstatetype::lparen_1) { - rp expr = this->gen_expr_; - - std::unique_ptr self = p_stack->pop_exprstate(); - - p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); } } @@ -787,24 +745,11 @@ namespace xo { *p_emit_expr = expr.promote(); return; case exprstatetype::defexpr: - /* unreachable. redirects to define_xs::on_expr() */ + case exprstatetype::parenexpr: + /* unreachable. redirects to define_xs::on_expr() etc */ assert(false); return; - case exprstatetype::lparen_0: { - this->exs_type_ = exprstatetype::lparen_1; /* wants on_rightparen */ - p_stack->push_exprstate(progress_xs::make(expr.promote())); - - return; - } - - case exprstatetype::lparen_1: { - this->gen_expr_ = expr.promote(); - - /* expect immediate incoming call, this time to on_rightparen() */ - return; - } - case exprstatetype::expect_rhs_expression: { std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ @@ -852,13 +797,8 @@ namespace xo { assert(false); return; case exprstatetype::defexpr: - /* unreachable - redirects to define_xs */ - assert(false); - return; - - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: - /* NOT IMPLEMENTED */ + case exprstatetype::parenexpr: + /* unreachable - redirects to define_xs etc */ assert(false); return; diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 3fe5e40d..e3d41fc9 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -5,6 +5,15 @@ namespace xo { namespace scm { + paren_xs::paren_xs() + : parenxs_type_{parenexprstatetype::lparen_0} + {} + + std::unique_ptr + paren_xs::lparen_0() { + return std::make_unique(paren_xs()); + } + bool paren_xs::admits_definition() const { return false; } @@ -23,13 +32,45 @@ namespace xo { bool paren_xs::admits_leftparen() const { /*unreachable*/ return false; } - /** TODO: fixme **/ bool - paren_xs::admits_rightparen() const { return exprstate::admits_rightparen(); } + paren_xs::admits_rightparen() const { + switch (parenxs_type_) { + case parenexprstatetype::lparen_0: + /* unreachable */ + assert(false); + return false; + + case parenexprstatetype::lparen_1: + return true; + + case parenexprstatetype::invalid: + case parenexprstatetype::n_parenexprstatetype: + /* unreachable */ + assert(false); + return false; + } + + return false; + } - /** TODO: fixme **/ bool - paren_xs::admits_f64() const { return exprstate::admits_f64(); } + paren_xs::admits_f64() const { + switch (parenxs_type_) { + case parenexprstatetype::lparen_0: + return true; + + case parenexprstatetype::lparen_1: + return false; + + case parenexprstatetype::invalid: + case parenexprstatetype::n_parenexprstatetype: + /* unreachable */ + assert(false); + return false; + } + + return false; + } void paren_xs::on_def(exprstatestack * /*p_stack*/) { @@ -124,7 +165,7 @@ namespace xo { xtag("state", *this))); } - if (this->exs_type_ == exprstatetype::lparen_1) { + if (this->parenxs_type_ == parenexprstatetype::lparen_1) { rp expr = this->gen_expr_; std::unique_ptr self = p_stack->pop_exprstate(); @@ -164,15 +205,15 @@ namespace xo { log && log(xtag("exstype", this->exs_type_), xtag("expr", expr)); - switch (this->exs_type_) { - case exprstatetype::lparen_0: { - this->exs_type_ = exprstatetype::lparen_1; /* wants on_rightparen */ + switch (this->parenxs_type_) { + case parenexprstatetype::lparen_0: { + this->parenxs_type_ = parenexprstatetype::lparen_1; /* wants on_rightparen */ p_stack->push_exprstate(progress_xs::make(expr.promote())); return; } - case exprstatetype::lparen_1: { + case parenexprstatetype::lparen_1: { this->gen_expr_ = expr.promote(); /* expect immediate incoming call, this time to on_rightparen() */ @@ -191,9 +232,9 @@ namespace xo { exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { - switch(this->exs_type_) { - case exprstatetype::lparen_0: - case exprstatetype::lparen_1: + switch(this->parenxs_type_) { + case parenexprstatetype::lparen_0: + case parenexprstatetype::lparen_1: /* NOT IMPLEMENTED */ assert(false); return; From b813e55194d163126faa609ae5f0a3a900be1216 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 12:58:11 -0400 Subject: [PATCH 1345/2524] xo-reader: refactor: demote exprstate.gen_expr to leaves that care --- include/xo/reader/exprstate.hpp | 29 ++++++++--------------------- include/xo/reader/paren_xs.hpp | 2 ++ include/xo/reader/progress_xs.hpp | 2 -- src/reader/define_xs.cpp | 3 +-- src/reader/progress_xs.cpp | 4 ++-- 5 files changed, 13 insertions(+), 27 deletions(-) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index fa0b466f..ced48160 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -56,34 +56,23 @@ namespace xo { public: exprstate() = default; - exprstate(exprstatetype exs_type, - rp candidate_expr) - : exs_type_{exs_type}, - gen_expr_{std::move(candidate_expr)} {} + exprstate(exprstatetype exs_type) + : exs_type_{exs_type} + {} virtual ~exprstate() = default; static std::unique_ptr expect_toplevel_expression_sequence() { - return std::make_unique(exprstate(exprstatetype::expect_toplevel_expression_sequence, nullptr)); + return std::make_unique(exprstate(exprstatetype::expect_toplevel_expression_sequence)); } static std::unique_ptr expect_rhs_expression() { - return std::make_unique(exprstate(exprstatetype::expect_rhs_expression, nullptr)); + return std::make_unique(exprstate(exprstatetype::expect_rhs_expression)); } static std::unique_ptr expect_symbol() { - return std::make_unique(exprstate(exprstatetype::expect_symbol, nullptr)); + return std::make_unique(exprstate(exprstatetype::expect_symbol)); } static std::unique_ptr expect_type() { - return std::make_unique(exprstate(exprstatetype::expect_type, nullptr)); + return std::make_unique(exprstate(exprstatetype::expect_type)); } -#ifdef RELOCATED - static std::unique_ptr make_expr_progress(rp expr) { - return std::make_unique(exprstate(exprstatetype::expr_progress, expr)); - } -#endif -#ifdef RELOCATED - static std::unique_ptr lparen_0() { - return std::make_unique(exprstate(exprstatetype::lparen_0, nullptr)); - } -#endif exprstatetype exs_type() const { return exs_type_; } @@ -144,10 +133,8 @@ namespace xo { rp * p_emit_expr); protected: + /** explicit subtype: identifies derived class **/ exprstatetype exs_type_; - - /** generic expression **/ - rp gen_expr_; }; /*exprstate*/ inline std::ostream & diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index a6a8eb3e..91d4a633 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -78,6 +78,8 @@ namespace xo { * lparen_0 **/ parenexprstatetype parenxs_type_; + /** populate expression (representing parenthesized value) here **/ + rp gen_expr_; }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 5b2ce28f..62fba624 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -58,10 +58,8 @@ namespace xo { virtual void print(std::ostream & os) const override; private: -#ifdef NOT_YET /** populate an expression here **/ rp gen_expr_; -#endif }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index d1f93984..5ecb82f5 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -10,8 +10,7 @@ namespace xo { } define_xs::define_xs(rp def_expr) - : exprstate(exprstatetype::defexpr, - nullptr /*gen_expr*/), + : exprstate(exprstatetype::defexpr), defxs_type_{defexprstatetype::def_0}, def_expr_{std::move(def_expr)} {} diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 6e1d743a..2070e909 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -10,8 +10,8 @@ namespace xo { } progress_xs::progress_xs(rp valex) - : exprstate(exprstatetype::expr_progress, - std::move(valex)) + : exprstate(exprstatetype::expr_progress), + gen_expr_{std::move(valex)} {} bool From 49cb0f0bba2c4975289fe759c3807515b459c7cb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 13:06:34 -0400 Subject: [PATCH 1346/2524] xo-reader: tidy: rename expstate.on_symbol() -> on_symbol_token() --- include/xo/reader/exprstate.hpp | 21 ++++++++++++++++----- include/xo/reader/paren_xs.hpp | 6 +++--- include/xo/reader/progress_xs.hpp | 6 +++--- src/reader/exprstate.cpp | 8 ++++---- src/reader/paren_xs.cpp | 6 +++--- src/reader/progress_xs.cpp | 6 +++--- 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index ced48160..6e2f5481 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -18,15 +18,24 @@ namespace xo { /** toplevel of some translation unit **/ expect_toplevel_expression_sequence, + /** handle define-expression + * see @ref define_xs + **/ defexpr, - /* handle parenthesized expression */ + /** handle parenthesized expression + * see @ref paren_xs + **/ parenexpr, expect_rhs_expression, expect_symbol, expect_type, + /** handle expression-in-progress, + * in case infix operators to follow + * see @ref progress_xs + **/ expr_progress, n_exprstatetype @@ -115,11 +124,13 @@ namespace xo { /** print human-readable representation on @p os **/ virtual void print(std::ostream & os) const; - public: + // ----- input methods ----- + virtual void on_def(exprstatestack * p_stack); - virtual void on_symbol(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); + /** handle incoming symbol token **/ + virtual void on_symbol_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); virtual void on_colon(exprstatestack * p_stack); virtual void on_semicolon(exprstatestack * p_stack, rp * p_emit_expr); diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 91d4a633..964b8dc3 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -53,9 +53,9 @@ namespace xo { exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) override; - virtual void on_symbol(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + virtual void on_symbol_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; virtual void on_colon(exprstatestack * p_stack) override; virtual void on_semicolon(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 62fba624..7d6a25f1 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -37,9 +37,9 @@ namespace xo { virtual void on_expr(ref::brw expr, exprstatestack * p_stack, rp * p_emit_expr) override; - virtual void on_symbol(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + virtual void on_symbol_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; virtual void on_typedescr(TypeDescr td, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) override; diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 351fbf5a..b83cad6a 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -348,9 +348,9 @@ namespace xo { } void - exprstate::on_symbol(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) + exprstate::on_symbol_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -662,7 +662,7 @@ namespace xo { return; case tokentype::tk_symbol: - this->on_symbol(tk, p_stack, p_emit_expr); + this->on_symbol_token(tk, p_stack, p_emit_expr); return; case tokentype::tk_leftparen: diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index e3d41fc9..f9ed033c 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -82,9 +82,9 @@ namespace xo { } void - paren_xs::on_symbol(const token_type & /*tk*/, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) + paren_xs::on_symbol_token(const token_type & /*tk*/, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 2070e909..7ac56de5 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -62,9 +62,9 @@ namespace xo { } void - progress_xs::on_symbol(const token_type & /*tk*/, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + progress_xs::on_symbol_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) { /* illegal input, e.g. * foo bar From 1fcac9485b8ad239c27d884f4152b48948e6d74e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 13:12:01 -0400 Subject: [PATCH 1347/2524] xo-reader: refactor: rename exprstate.on_colon() -> on_colon_token --- include/xo/reader/define_xs.hpp | 3 ++- include/xo/reader/exprstate.hpp | 4 +++- include/xo/reader/paren_xs.hpp | 3 ++- include/xo/reader/progress_xs.hpp | 3 ++- src/reader/define_xs.cpp | 4 +++- src/reader/exprstate.cpp | 6 ++++-- src/reader/paren_xs.cpp | 3 ++- src/reader/progress_xs.cpp | 4 +++- 8 files changed, 21 insertions(+), 9 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 3a740a32..bee7d24f 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -60,7 +60,8 @@ namespace xo { virtual void on_typedescr(TypeDescr td, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) override; - virtual void on_colon(exprstatestack * p_stack) override; + virtual void on_colon_token(const token_type & tk, + exprstatestack * p_stack) override; virtual void on_semicolon(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; virtual void on_singleassign(exprstatestack * p_stack) override; diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 6e2f5481..2f0c9e6a 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -131,7 +131,9 @@ namespace xo { virtual void on_symbol_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); - virtual void on_colon(exprstatestack * p_stack); + /** handle incoming ':' token **/ + virtual void on_colon_token(const token_type & tk, + exprstatestack * p_stack); virtual void on_semicolon(exprstatestack * p_stack, rp * p_emit_expr); virtual void on_singleassign(exprstatestack * p_stack); diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 964b8dc3..fa63f212 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -56,7 +56,8 @@ namespace xo { virtual void on_symbol_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) override; - virtual void on_colon(exprstatestack * p_stack) override; + virtual void on_colon_token(const token_type & tk, + exprstatestack * p_stack) override; virtual void on_semicolon(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; virtual void on_singleassign(exprstatestack * p_stack) override; diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 7d6a25f1..e0e2c7db 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -43,7 +43,8 @@ namespace xo { virtual void on_typedescr(TypeDescr td, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) override; - virtual void on_colon(exprstatestack * p_stack) override; + virtual void on_colon_token(const token_type & tk, + exprstatestack * p_stack) override; virtual void on_semicolon(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; virtual void on_singleassign(exprstatestack * p_stack) override; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 5ecb82f5..82081331 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -326,7 +326,9 @@ namespace xo { } void - define_xs::on_colon(exprstatestack * p_stack) { + define_xs::on_colon_token(const token_type & /*tk*/, + exprstatestack * p_stack) + { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index b83cad6a..bb8c1a18 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -504,7 +504,9 @@ namespace xo { } void - exprstate::on_colon(exprstatestack * /*p_stack*/) { + exprstate::on_colon_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/) + { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -686,7 +688,7 @@ namespace xo { return; case tokentype::tk_colon: - this->on_colon(p_stack); + this->on_colon_token(tk, p_stack); return; case tokentype::tk_doublecolon: diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index f9ed033c..ebc07527 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -108,7 +108,8 @@ namespace xo { } void - paren_xs::on_colon(exprstatestack * /*p_stack*/) + paren_xs::on_colon_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/) { constexpr const char * c_self_name = "paren_xs::on_colon"; diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 7ac56de5..32964064 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -82,7 +82,9 @@ namespace xo { } void - progress_xs::on_colon(exprstatestack * /*p_stack*/) { + progress_xs::on_colon_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/) + { constexpr const char * self_name = "progress_xs::on_colon"; throw std::runtime_error(tostr(self_name, From 544c1def4a00fc6a8a759b0e58e28f97ceb77a25 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 13:16:52 -0400 Subject: [PATCH 1348/2524] xo-reader: rename+: exprstate.on_semicolon() -> on_semicolon_token() --- include/xo/reader/define_xs.hpp | 5 +++-- include/xo/reader/exprstate.hpp | 6 ++++-- include/xo/reader/paren_xs.hpp | 5 +++-- include/xo/reader/progress_xs.hpp | 5 +++-- src/reader/define_xs.cpp | 5 +++-- src/reader/exprstate.cpp | 7 ++++--- src/reader/paren_xs.cpp | 5 +++-- src/reader/progress_xs.cpp | 7 ++++--- 8 files changed, 27 insertions(+), 18 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index bee7d24f..b90cde31 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -62,8 +62,9 @@ namespace xo { rp * /*p_emit_expr*/) override; virtual void on_colon_token(const token_type & tk, exprstatestack * p_stack) override; - virtual void on_semicolon(exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + virtual void on_semicolon_token(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; virtual void on_singleassign(exprstatestack * p_stack) override; virtual void on_leftparen(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 2f0c9e6a..c7a419d3 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -134,8 +134,10 @@ namespace xo { /** handle incoming ':' token **/ virtual void on_colon_token(const token_type & tk, exprstatestack * p_stack); - virtual void on_semicolon(exprstatestack * p_stack, - rp * p_emit_expr); + /** handle incoming ';' token **/ + virtual void on_semicolon_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); virtual void on_singleassign(exprstatestack * p_stack); virtual void on_leftparen(exprstatestack * p_stack, rp * p_emit_expr); diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index fa63f212..ed2ad87b 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -58,8 +58,9 @@ namespace xo { rp * p_emit_expr) override; virtual void on_colon_token(const token_type & tk, exprstatestack * p_stack) override; - virtual void on_semicolon(exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + virtual void on_semicolon_token(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; virtual void on_singleassign(exprstatestack * p_stack) override; virtual void on_leftparen(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index e0e2c7db..a6d3d508 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -45,8 +45,9 @@ namespace xo { rp * /*p_emit_expr*/) override; virtual void on_colon_token(const token_type & tk, exprstatestack * p_stack) override; - virtual void on_semicolon(exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + virtual void on_semicolon_token(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; virtual void on_singleassign(exprstatestack * p_stack) override; virtual void on_leftparen(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 82081331..512a214b 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -352,8 +352,9 @@ namespace xo { } void - define_xs::on_semicolon(exprstatestack * p_stack, - rp * p_emit_expr) + define_xs::on_semicolon_token(const token_type & /*tk*/, + exprstatestack * p_stack, + rp * p_emit_expr) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index bb8c1a18..9902e75e 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -524,8 +524,9 @@ namespace xo { } void - exprstate::on_semicolon(exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + exprstate::on_semicolon_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -696,7 +697,7 @@ namespace xo { return; case tokentype::tk_semicolon: - this->on_semicolon(p_stack, p_emit_expr); + this->on_semicolon_token(tk, p_stack, p_emit_expr); return; case tokentype::tk_singleassign: diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index ebc07527..f8ae4f17 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -119,8 +119,9 @@ namespace xo { } void - paren_xs::on_semicolon(exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + paren_xs::on_semicolon_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) { constexpr const char * c_self_name = "paren_xs::on_semicolon"; diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 32964064..59df672a 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -93,8 +93,9 @@ namespace xo { } void - progress_xs::on_semicolon(exprstatestack * p_stack, - rp * p_emit_expr) + progress_xs::on_semicolon_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -121,7 +122,7 @@ namespace xo { * f. now deliver semicolon; [lparen_1] rejects */ - p_stack->top_exprstate().on_semicolon(p_stack, p_emit_expr); + p_stack->top_exprstate().on_semicolon_token(tk, p_stack, p_emit_expr); } void From 2cff2b5ca78ad3e99781cf6b75c8a07b1c987f06 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 13:21:18 -0400 Subject: [PATCH 1349/2524] xo-reader: rename+: exprstate.on_singleassign() + explicit token --- include/xo/reader/define_xs.hpp | 3 ++- include/xo/reader/exprstate.hpp | 4 +++- include/xo/reader/paren_xs.hpp | 3 ++- include/xo/reader/progress_xs.hpp | 3 ++- src/reader/define_xs.cpp | 3 ++- src/reader/exprstate.cpp | 5 +++-- src/reader/paren_xs.cpp | 3 ++- src/reader/progress_xs.cpp | 4 +++- 8 files changed, 19 insertions(+), 9 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index b90cde31..e90abf9f 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -65,7 +65,8 @@ namespace xo { virtual void on_semicolon_token(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; - virtual void on_singleassign(exprstatestack * p_stack) override; + virtual void on_singleassign_token(const token_type & tk, + exprstatestack * p_stack) override; virtual void on_leftparen(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; virtual void on_rightparen(exprstatestack * p_stack, diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index c7a419d3..f8921dc9 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -138,7 +138,9 @@ namespace xo { virtual void on_semicolon_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); - virtual void on_singleassign(exprstatestack * p_stack); + /** handle incoming '=' token **/ + virtual void on_singleassign_token(const token_type & tk, + exprstatestack * p_stack); virtual void on_leftparen(exprstatestack * p_stack, rp * p_emit_expr); virtual void on_rightparen(exprstatestack * p_stack, diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index ed2ad87b..821fef13 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -61,7 +61,8 @@ namespace xo { virtual void on_semicolon_token(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; - virtual void on_singleassign(exprstatestack * p_stack) override; + virtual void on_singleassign_token(const token_type & tk, + exprstatestack * p_stack) override; virtual void on_leftparen(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; virtual void on_rightparen(exprstatestack * p_stack, diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index a6d3d508..431ad0fd 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -48,7 +48,8 @@ namespace xo { virtual void on_semicolon_token(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; - virtual void on_singleassign(exprstatestack * p_stack) override; + virtual void on_singleassign_token(const token_type & tk, + exprstatestack * p_stack) override; virtual void on_leftparen(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; virtual void on_rightparen(exprstatestack * p_stack, diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 512a214b..be531be3 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -382,7 +382,8 @@ namespace xo { } void - define_xs::on_singleassign(exprstatestack * p_stack) + define_xs::on_singleassign_token(const token_type & /*tk*/, + exprstatestack * p_stack) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 9902e75e..236bc895 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -544,7 +544,8 @@ namespace xo { } void - exprstate::on_singleassign(exprstatestack * /*p_stack*/) { + exprstate::on_singleassign_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -701,7 +702,7 @@ namespace xo { return; case tokentype::tk_singleassign: - this->on_singleassign(p_stack); + this->on_singleassign_token(tk, p_stack); return; case tokentype::tk_assign: diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index f8ae4f17..8b7ee84d 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -131,7 +131,8 @@ namespace xo { } void - paren_xs::on_singleassign(exprstatestack * /*p_stack*/) + paren_xs::on_singleassign_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/) { constexpr const char * c_self_name = "paren_xs::on_singleassign"; diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 59df672a..d8ab19d8 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -126,7 +126,9 @@ namespace xo { } void - progress_xs::on_singleassign(exprstatestack * /*p_stack*/) { + progress_xs::on_singleassign_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/) + { constexpr const char * self_name = "progress_xs::on_singleassign"; throw std::runtime_error(tostr(self_name, From 8d1ae35075891fea9f2d372bc97b1f5c68e08cfd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 13:25:59 -0400 Subject: [PATCH 1350/2524] xo-reader: rename+: exprstate.on_leftparen() -> on_leftparen_token() --- include/xo/reader/define_xs.hpp | 5 +++-- include/xo/reader/exprstate.hpp | 4 +++- include/xo/reader/paren_xs.hpp | 5 +++-- include/xo/reader/progress_xs.hpp | 5 +++-- src/reader/define_xs.cpp | 5 +++-- src/reader/exprstate.cpp | 7 ++++--- src/reader/paren_xs.cpp | 5 +++-- src/reader/progress_xs.cpp | 5 +++-- 8 files changed, 25 insertions(+), 16 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index e90abf9f..bf12e566 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -67,8 +67,9 @@ namespace xo { rp * /*p_emit_expr*/) override; virtual void on_singleassign_token(const token_type & tk, exprstatestack * p_stack) override; - virtual void on_leftparen(exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + virtual void on_leftparen_token(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; virtual void on_rightparen(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; virtual void on_f64(const token_type & tk, diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index f8921dc9..0a41e5a0 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -141,7 +141,9 @@ namespace xo { /** handle incoming '=' token **/ virtual void on_singleassign_token(const token_type & tk, exprstatestack * p_stack); - virtual void on_leftparen(exprstatestack * p_stack, + /** handle incoming '(' token **/ + virtual void on_leftparen_token(const token_type & tk, + exprstatestack * p_stack, rp * p_emit_expr); virtual void on_rightparen(exprstatestack * p_stack, rp * p_emit_expr); diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 821fef13..9a9dba83 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -63,8 +63,9 @@ namespace xo { rp * /*p_emit_expr*/) override; virtual void on_singleassign_token(const token_type & tk, exprstatestack * p_stack) override; - virtual void on_leftparen(exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + virtual void on_leftparen_token(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; virtual void on_rightparen(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; virtual void on_f64(const token_type & tk, diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 431ad0fd..a2e8da6c 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -50,8 +50,9 @@ namespace xo { rp * /*p_emit_expr*/) override; virtual void on_singleassign_token(const token_type & tk, exprstatestack * p_stack) override; - virtual void on_leftparen(exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + virtual void on_leftparen_token(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; virtual void on_rightparen(exprstatestack * p_stack, rp * /*p_emit_expr*/) override; virtual void on_f64(const token_type & tk, diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index be531be3..3373c0a7 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -409,8 +409,9 @@ namespace xo { } void - define_xs::on_leftparen(exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + define_xs::on_leftparen_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 236bc895..3c53bf74 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -562,8 +562,9 @@ namespace xo { } void - exprstate::on_leftparen(exprstatestack * p_stack, - rp * /*p_emit_expr*/) + exprstate::on_leftparen_token(const token_type & /*tk*/, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -670,7 +671,7 @@ namespace xo { return; case tokentype::tk_leftparen: - this->on_leftparen(p_stack, p_emit_expr); + this->on_leftparen_token(tk, p_stack, p_emit_expr); return; case tokentype::tk_rightparen: diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 8b7ee84d..afa96b47 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -142,8 +142,9 @@ namespace xo { } void - paren_xs::on_leftparen(exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + paren_xs::on_leftparen_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) { constexpr const char * c_self_name = "paren_xs::on_leftparen"; diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index d8ab19d8..0118132c 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -137,8 +137,9 @@ namespace xo { } void - progress_xs::on_leftparen(exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + progress_xs::on_leftparen_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); From a51f1d65dbc003fa804906c19986065c23c33d59 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 13:30:41 -0400 Subject: [PATCH 1351/2524] xo-reader: rename+: exprstate.on_rightparen() + token arg --- include/xo/reader/define_xs.hpp | 5 +++-- include/xo/reader/exprstate.hpp | 6 ++++-- include/xo/reader/paren_xs.hpp | 5 +++-- include/xo/reader/progress_xs.hpp | 5 +++-- src/reader/define_xs.cpp | 5 +++-- src/reader/exprstate.cpp | 7 ++++--- src/reader/paren_xs.cpp | 5 +++-- src/reader/progress_xs.cpp | 7 ++++--- 8 files changed, 27 insertions(+), 18 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index bf12e566..87871ac9 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -70,8 +70,9 @@ namespace xo { virtual void on_leftparen_token(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; - virtual void on_rightparen(exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + virtual void on_rightparen_token(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; virtual void on_f64(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 0a41e5a0..820ec9d3 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -145,8 +145,10 @@ namespace xo { virtual void on_leftparen_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); - virtual void on_rightparen(exprstatestack * p_stack, - rp * p_emit_expr); + /** handle incoming ')' token **/ + virtual void on_rightparen_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); virtual void on_f64(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 9a9dba83..ce646bfb 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -66,8 +66,9 @@ namespace xo { virtual void on_leftparen_token(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; - virtual void on_rightparen(exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + virtual void on_rightparen_token(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; virtual void on_f64(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index a2e8da6c..0d36a0b3 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -53,8 +53,9 @@ namespace xo { virtual void on_leftparen_token(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; - virtual void on_rightparen(exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + virtual void on_rightparen_token(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; virtual void on_f64(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 3373c0a7..dba2118a 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -429,8 +429,9 @@ namespace xo { } void - define_xs::on_rightparen(exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + define_xs::on_rightparen_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 3c53bf74..d625a758 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -586,8 +586,9 @@ namespace xo { } void - exprstate::on_rightparen(exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + exprstate::on_rightparen_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -675,7 +676,7 @@ namespace xo { return; case tokentype::tk_rightparen: - this->on_rightparen(p_stack, p_emit_expr); + this->on_rightparen_token(tk, p_stack, p_emit_expr); return; case tokentype::tk_leftbracket: diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index afa96b47..2b92419e 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -154,8 +154,9 @@ namespace xo { } void - paren_xs::on_rightparen(exprstatestack * p_stack, - rp * p_emit_expr) + paren_xs::on_rightparen_token(const token_type & /*tk*/, + exprstatestack * p_stack, + rp * p_emit_expr) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 0118132c..f2ccf0ea 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -152,8 +152,9 @@ namespace xo { } void - progress_xs::on_rightparen(exprstatestack * p_stack, - rp * p_emit_expr) + progress_xs::on_rightparen_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -186,7 +187,7 @@ namespace xo { p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); /* now deliver rightparen */ - p_stack->top_exprstate().on_rightparen(p_stack, p_emit_expr); + p_stack->top_exprstate().on_rightparen_token(tk, p_stack, p_emit_expr); } From 892b332a20ca090f36d77f80f6d1a89982e211de Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 13:36:33 -0400 Subject: [PATCH 1352/2524] xo-reader: rename: exprstate.on_f64() -> on_f64_token() --- include/xo/reader/define_xs.hpp | 6 +++--- include/xo/reader/exprstate.hpp | 7 ++++--- include/xo/reader/paren_xs.hpp | 6 +++--- include/xo/reader/progress_xs.hpp | 6 +++--- src/reader/define_xs.cpp | 6 +++--- src/reader/exprstate.cpp | 8 ++++---- src/reader/paren_xs.cpp | 6 +++--- src/reader/progress_xs.cpp | 6 +++--- 8 files changed, 26 insertions(+), 25 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 87871ac9..10d53459 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -73,9 +73,9 @@ namespace xo { virtual void on_rightparen_token(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; - virtual void on_f64(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + virtual void on_f64_token(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; virtual void print(std::ostream & os) const override; diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 820ec9d3..943b70bc 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -149,9 +149,10 @@ namespace xo { virtual void on_rightparen_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); - virtual void on_f64(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); + /** handle incoming floating-point-literal token **/ + virtual void on_f64_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); protected: /** explicit subtype: identifies derived class **/ diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index ce646bfb..8bf137b5 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -69,9 +69,9 @@ namespace xo { virtual void on_rightparen_token(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; - virtual void on_f64(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + virtual void on_f64_token(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; virtual void print(std::ostream & os) const override; diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 0d36a0b3..3203a274 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -56,9 +56,9 @@ namespace xo { virtual void on_rightparen_token(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; - virtual void on_f64(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + virtual void on_f64_token(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) override; virtual void print(std::ostream & os) const override; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index dba2118a..d7bccf82 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -449,9 +449,9 @@ namespace xo { } void - define_xs::on_f64(const token_type & /*tk*/, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + define_xs::on_f64_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index d625a758..1a33a503 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -610,9 +610,9 @@ namespace xo { } void - exprstate::on_f64(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) + exprstate::on_f64_token(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -660,7 +660,7 @@ namespace xo { return; case tokentype::tk_f64: - this->on_f64(tk, p_stack, p_emit_expr); + this->on_f64_token(tk, p_stack, p_emit_expr); return; case tokentype::tk_string: diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 2b92419e..5446cccb 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -180,9 +180,9 @@ namespace xo { } void - paren_xs::on_f64(const token_type & /*tk*/, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + paren_xs::on_f64_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index f2ccf0ea..bb51cf02 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -192,9 +192,9 @@ namespace xo { } void - progress_xs::on_f64(const token_type & /*tk*/, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + progress_xs::on_f64_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); From 60a7ec1cd04908600eab4da7217a6dd5e258eeae Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 13:42:50 -0400 Subject: [PATCH 1353/2524] xo-reader: rename+: exprstate.on_def() -> on_def_token() + token arg --- include/xo/reader/exprstate.hpp | 4 +++- include/xo/reader/paren_xs.hpp | 5 ++--- include/xo/reader/progress_xs.hpp | 6 +++--- src/reader/exprstate.cpp | 5 +++-- src/reader/paren_xs.cpp | 4 +++- src/reader/progress_xs.cpp | 4 +++- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 943b70bc..467d2eab 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -126,7 +126,9 @@ namespace xo { // ----- input methods ----- - virtual void on_def(exprstatestack * p_stack); + /** handle incoming 'def' token **/ + virtual void on_def_token(const token_type & tk, + exprstatestack * p_stack); /** handle incoming symbol token **/ virtual void on_symbol_token(const token_type & tk, exprstatestack * p_stack, diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 8bf137b5..5992837a 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -40,9 +40,6 @@ namespace xo { virtual bool admits_rightparen() const override; virtual bool admits_f64() const override; - // virtual void on_f64(..) override - virtual void on_def(exprstatestack * p_stack) override; - virtual void on_expr(ref::brw expr, exprstatestack * p_stack, rp * p_emit_expr) override; @@ -53,6 +50,8 @@ namespace xo { exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) override; + virtual void on_def_token(const token_type & tk, + exprstatestack * p_stack) override; virtual void on_symbol_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) override; diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 3203a274..0a0516cf 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -31,9 +31,6 @@ namespace xo { virtual bool admits_rightparen() const override; virtual bool admits_f64() const override; - // virtual void on_f64(..) override - virtual void on_def(exprstatestack * p_stack) override; - virtual void on_expr(ref::brw expr, exprstatestack * p_stack, rp * p_emit_expr) override; @@ -43,6 +40,9 @@ namespace xo { virtual void on_typedescr(TypeDescr td, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) override; + + virtual void on_def_token(const token_type & tk, + exprstatestack * p_stack) override; virtual void on_colon_token(const token_type & tk, exprstatestack * p_stack) override; virtual void on_semicolon_token(const token_type & tk, diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 1a33a503..5227135c 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -320,7 +320,8 @@ namespace xo { } void - exprstate::on_def(exprstatestack * p_stack) { + exprstate::on_def_token(const token_type & /*tk*/, + exprstatestack * p_stack) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -652,7 +653,7 @@ namespace xo { switch (tk.tk_type()) { case tokentype::tk_def: - this->on_def(p_stack); + this->on_def_token(tk, p_stack); return; case tokentype::tk_i64: diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 5446cccb..20d7573d 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -73,7 +73,9 @@ namespace xo { } void - paren_xs::on_def(exprstatestack * /*p_stack*/) { + paren_xs::on_def_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/) + { constexpr const char * c_self_name = "paren_xs::on_def"; throw std::runtime_error(tostr(c_self_name, diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index bb51cf02..19df5489 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -43,7 +43,9 @@ namespace xo { progress_xs::admits_f64() const { return false; } void - progress_xs::on_def(exprstatestack * /*p_stack*/) { + progress_xs::on_def_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/) + { constexpr const char * self_name = "progress_xs::on_def"; /* nothing here - admits_definition unconditionally false */ From 74099cacab8979b8deddf3614787a1913f3647e9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 13:57:42 -0400 Subject: [PATCH 1354/2524] xo-reader: uncopy: with + exprstate.illegal_input_error() --- include/xo/reader/exprstate.hpp | 7 ++++++ src/reader/define_xs.cpp | 27 +++++++------------- src/reader/exprstate.cpp | 34 ++++++++++++++----------- src/reader/paren_xs.cpp | 44 +++++++++++---------------------- src/reader/progress_xs.cpp | 30 ++++++++-------------- 5 files changed, 61 insertions(+), 81 deletions(-) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 467d2eab..c775a5b9 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -156,6 +156,13 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr); + protected: + /** throw exception when next token is inconsistent with + * parsing state + **/ + void illegal_input_error(const char * self_name, + const token_type & tk) const; + protected: /** explicit subtype: identifies derived class **/ exprstatetype exs_type_; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index d7bccf82..95fde120 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -382,7 +382,7 @@ namespace xo { } void - define_xs::on_singleassign_token(const token_type & /*tk*/, + define_xs::on_singleassign_token(const token_type & tk, exprstatestack * p_stack) { constexpr bool c_debug_flag = true; @@ -390,11 +390,8 @@ namespace xo { constexpr const char * self_name = "exprstate::on_singleassign"; - if (!this->admits_singleassign()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected equals for parsing state", - xtag("state", *this))); + if (!this->admits_singleassign()) { + this->illegal_input_error(self_name, tk); } if ((this->defxs_type_ == defexprstatetype::def_1) @@ -409,7 +406,7 @@ namespace xo { } void - define_xs::on_leftparen_token(const token_type & /*tk*/, + define_xs::on_leftparen_token(const token_type & tk, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { @@ -420,16 +417,14 @@ namespace xo { if (!this->admits_leftparen()) { - throw std::runtime_error(tostr(self_name, - ": unexpected leftparen '(' for parsing state", - xtag("state", *this))); + this->illegal_input_error(self_name, tk); } assert(false); /* inserting this during refactor...? */ } void - define_xs::on_rightparen_token(const token_type & /*tk*/, + define_xs::on_rightparen_token(const token_type & tk, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { @@ -440,16 +435,14 @@ namespace xo { if (!this->admits_rightparen()) { - throw std::runtime_error(tostr(self_name, - ": unexpected rightparen ')' for parsing state", - xtag("state", *this))); + this->illegal_input_error(self_name, tk); } assert(false); /* inserting this during refactor..? */ } void - define_xs::on_f64_token(const token_type & /*tk*/, + define_xs::on_f64_token(const token_type & tk, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { @@ -460,9 +453,7 @@ namespace xo { if (!this->admits_f64()) { - throw std::runtime_error(tostr(self_name, - ": unexpected floating-point literal for parsing state", - xtag("state", *this))); + this->illegal_input_error(self_name, tk); } assert(false); diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 5227135c..d6242730 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -320,19 +320,18 @@ namespace xo { } void - exprstate::on_def_token(const token_type & /*tk*/, - exprstatestack * p_stack) { + exprstate::on_def_token(const token_type & tk, + exprstatestack * p_stack) + { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - constexpr const char * self_name = "exprstate::on_def"; + constexpr const char * c_self_name = "exprstate::on_def_token"; /* lots of illegal states */ if (!this->admits_definition()) { - throw std::runtime_error(tostr(self_name, - ": unexpected keyword 'def' for parsing state", - xtag("state", *this))); + this->illegal_input_error(c_self_name, tk); } p_stack->push_exprstate(define_xs::def_0()); @@ -358,20 +357,16 @@ namespace xo { log && log(xtag("exstype", p_stack->top_exprstate().exs_type())); - constexpr const char * self_name = "exprstate::on_symbol"; + constexpr const char * c_self_name = "exprstate::on_symbol_token"; if (!this->admits_symbol()) { - throw std::runtime_error - (tostr(self_name, - ": unexpected symbol-token for parsing state", - xtag("symbol", tk), - xtag("state", *this))); + this->illegal_input_error(c_self_name, tk); } switch (this->exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: throw std::runtime_error - (tostr(self_name, + (tostr(c_self_name, ": unexpected symbol-token at top-level", " (expecting decl|def)", xtag("symbol", tk))); @@ -442,7 +437,7 @@ namespace xo { if (!td) { throw std::runtime_error - (tostr(self_name, + (tostr(c_self_name, ": unknown type name", " (expecting f64|f32|i16|i32|i64)", xtag("typename", tk.text()))); @@ -835,6 +830,17 @@ namespace xo { os << ">"; } + void + exprstate::illegal_input_error(const char * self_name, + const token_type & tk) const + { + throw std::runtime_error + (tostr(self_name, + ": unexpected input token for parsing state", + xtag("token", tk), + xtag("state", *this))); + } + // ----- exprstatestack ----- exprstate & diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 20d7573d..6d464178 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -73,14 +73,12 @@ namespace xo { } void - paren_xs::on_def_token(const token_type & /*tk*/, + paren_xs::on_def_token(const token_type & tk, exprstatestack * /*p_stack*/) { constexpr const char * c_self_name = "paren_xs::on_def"; - throw std::runtime_error(tostr(c_self_name, - ": unexpected keyword 'def' for parsing state", - xtag("state", *this))); + this->illegal_input_error(c_self_name, tk); } void @@ -110,66 +108,56 @@ namespace xo { } void - paren_xs::on_colon_token(const token_type & /*tk*/, + paren_xs::on_colon_token(const token_type & tk, exprstatestack * /*p_stack*/) { constexpr const char * c_self_name = "paren_xs::on_colon"; - throw std::runtime_error(tostr(c_self_name, - ": unexpected colon for parsing state", - xtag("state", *this))); + this->illegal_input_error(c_self_name, tk); } void - paren_xs::on_semicolon_token(const token_type & /*tk*/, + paren_xs::on_semicolon_token(const token_type & tk, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { constexpr const char * c_self_name = "paren_xs::on_semicolon"; - throw std::runtime_error(tostr(c_self_name, - ": unexpected semicolon for parsing state", - xtag("state", *this))); + this->illegal_input_error(c_self_name, tk); } void - paren_xs::on_singleassign_token(const token_type & /*tk*/, + paren_xs::on_singleassign_token(const token_type & tk, exprstatestack * /*p_stack*/) { constexpr const char * c_self_name = "paren_xs::on_singleassign"; - throw std::runtime_error(tostr(c_self_name, - ": unexpected equals for parsing state", - xtag("state", *this))); + this->illegal_input_error(c_self_name, tk); } void - paren_xs::on_leftparen_token(const token_type & /*tk*/, + paren_xs::on_leftparen_token(const token_type & tk, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { constexpr const char * c_self_name = "paren_xs::on_leftparen"; - throw std::runtime_error(tostr(c_self_name, - ": unexpected leftparen '(' for parsing state", - xtag("state", *this))); + this->illegal_input_error(c_self_name, tk); } void - paren_xs::on_rightparen_token(const token_type & /*tk*/, + paren_xs::on_rightparen_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - constexpr const char * self_name = "paren_xs::on_rightparen"; + constexpr const char * c_self_name = "paren_xs::on_rightparen"; if (!this->admits_rightparen()) { - throw std::runtime_error(tostr(self_name, - ": unexpected rightparen ')' for parsing state", - xtag("state", *this))); + this->illegal_input_error(c_self_name, tk); } if (this->parenxs_type_ == parenexprstatetype::lparen_1) { @@ -182,7 +170,7 @@ namespace xo { } void - paren_xs::on_f64_token(const token_type & /*tk*/, + paren_xs::on_f64_token(const token_type & tk, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { @@ -193,9 +181,7 @@ namespace xo { if (!this->admits_f64()) { - throw std::runtime_error(tostr(c_self_name, - ": unexpected floating-point literal for parsing state", - xtag("state", *this))); + this->illegal_input_error(c_self_name, tk); } assert(false); diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 19df5489..a355229e 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -43,15 +43,13 @@ namespace xo { progress_xs::admits_f64() const { return false; } void - progress_xs::on_def_token(const token_type & /*tk*/, + progress_xs::on_def_token(const token_type & tk, exprstatestack * /*p_stack*/) { constexpr const char * self_name = "progress_xs::on_def"; /* nothing here - admits_definition unconditionally false */ - throw std::runtime_error(tostr(self_name, - ": unexpected keyword 'def' for parsing state", - xtag("state", *this))); + this->illegal_input_error(self_name, tk) ; } void @@ -84,14 +82,12 @@ namespace xo { } void - progress_xs::on_colon_token(const token_type & /*tk*/, + progress_xs::on_colon_token(const token_type & tk, exprstatestack * /*p_stack*/) { constexpr const char * self_name = "progress_xs::on_colon"; - throw std::runtime_error(tostr(self_name, - ": unexpected colon for parsing state", - xtag("state", *this))); + this->illegal_input_error(self_name, tk); } void @@ -128,18 +124,16 @@ namespace xo { } void - progress_xs::on_singleassign_token(const token_type & /*tk*/, + progress_xs::on_singleassign_token(const token_type & tk, exprstatestack * /*p_stack*/) { constexpr const char * self_name = "progress_xs::on_singleassign"; - throw std::runtime_error(tostr(self_name, - ": unexpected equals for parsing state", - xtag("state", *this))); + this->illegal_input_error(self_name, tk); } void - progress_xs::on_leftparen_token(const token_type & /*tk*/, + progress_xs::on_leftparen_token(const token_type & tk, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { @@ -148,9 +142,7 @@ namespace xo { constexpr const char * self_name = "exprstate::on_leftparen"; - throw std::runtime_error(tostr(self_name, - ": unexpected leftparen '(' for parsing state", - xtag("state", *this))); + this->illegal_input_error(self_name, tk); } void @@ -194,7 +186,7 @@ namespace xo { } void - progress_xs::on_f64_token(const token_type & /*tk*/, + progress_xs::on_f64_token(const token_type & tk, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { @@ -203,9 +195,7 @@ namespace xo { constexpr const char * self_name = "progress_xs::on_f64"; - throw std::runtime_error(tostr(self_name, - ": unexpected floating-point literal for parsing state", - xtag("state", *this))); + this->illegal_input_error(self_name, tk); } void From 04f79eaf013ba9f64bb2107d54bc61a51a97bfad Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 14:10:16 -0400 Subject: [PATCH 1355/2524] xo-reader: wip: + exprseq_xs class --- include/xo/reader/exprseq_xs.hpp | 28 +++++++++++++++++++++++++++ src/reader/CMakeLists.txt | 3 ++- src/reader/exprseq_xs.cpp | 33 ++++++++++++++++++++++++++++++++ src/reader/exprstate.cpp | 2 ++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 include/xo/reader/exprseq_xs.hpp create mode 100644 src/reader/exprseq_xs.cpp diff --git a/include/xo/reader/exprseq_xs.hpp b/include/xo/reader/exprseq_xs.hpp new file mode 100644 index 00000000..28a17088 --- /dev/null +++ b/include/xo/reader/exprseq_xs.hpp @@ -0,0 +1,28 @@ +/** @file exprseq_xs.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "exprstate.hpp" +//#include + +namespace xo { + namespace scm { + /** @class exprseq_xs + * @brief parsing state-machine for top-level expression sequence + * + **/ + class exprseq_xs : public exprstate { + public: + // ----- token input methods ----- + + virtual void on_def_token(const token_type & tk, + exprstatestack * p_stack) override; + }; + } /*namespace scm*/ +} /*namespace xo*/ + + +/** end exprseq_xs.hpp **/ diff --git a/src/reader/CMakeLists.txt b/src/reader/CMakeLists.txt index 4741fa3d..e75e248a 100644 --- a/src/reader/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -7,7 +7,8 @@ set(SELF_SRCS exprstate.cpp define_xs.cpp progress_xs.cpp - paren_xs.cpp) + paren_xs.cpp + exprseq_xs.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_dependency(${SELF_LIB} xo_expression) diff --git a/src/reader/exprseq_xs.cpp b/src/reader/exprseq_xs.cpp new file mode 100644 index 00000000..012e9b2e --- /dev/null +++ b/src/reader/exprseq_xs.cpp @@ -0,0 +1,33 @@ +/* @file exprseq_xs.cpp */ + +#include "exprseq_xs.hpp" +#include "define_xs.hpp" + +namespace xo { + namespace scm { + void + exprseq_xs::on_def_token(const token_type & /*tk*/, + exprstatestack * p_stack) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + //constexpr const char * c_self_name = "exprseq_xs::on_def_token"; + + p_stack->push_exprstate(define_xs::def_0()); + + /* todo: replace: + * expect_symbol_or_function_signature() + */ + p_stack->push_exprstate(exprstate::expect_symbol()); + + /* keyword 'def' introduces a definition: + * def pi : f64 = 3.14159265 + * def sq(x : f64) -> f64 { (x * x) } + */ + } + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end exprseq_xs.cpp */ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index d6242730..6116b349 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -41,6 +41,7 @@ namespace xo { return "???"; } +#ifdef OBSOLETE bool exprstate::admits_definition() const { switch (exs_type_) { @@ -69,6 +70,7 @@ namespace xo { return false; } +#endif bool exprstate::admits_symbol() const { From 424a4cd0f1789923ac7e7a9cd132029ef09c0ea8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 19:43:00 -0400 Subject: [PATCH 1356/2524] xo-reader: simplify: adopt exprseq_xs + drop .admits_definition() --- include/xo/reader/define_xs.hpp | 2 ++ include/xo/reader/exprseq_xs.hpp | 5 +++++ include/xo/reader/exprstate.hpp | 4 ++++ include/xo/reader/paren_xs.hpp | 2 ++ include/xo/reader/progress_xs.hpp | 2 ++ src/reader/define_xs.cpp | 2 ++ src/reader/exprseq_xs.cpp | 12 +++++++++++- src/reader/exprstate.cpp | 6 +++++- src/reader/paren_xs.cpp | 2 ++ src/reader/parser.cpp | 3 ++- src/reader/progress_xs.cpp | 2 ++ 11 files changed, 39 insertions(+), 3 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 10d53459..dafafed4 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -42,7 +42,9 @@ namespace xo { defexprstatetype defxs_type() const { return defxs_type_; } +#ifdef OBSOLETE virtual bool admits_definition() const override; +#endif virtual bool admits_symbol() const override; virtual bool admits_colon() const override; virtual bool admits_semicolon() const override; diff --git a/include/xo/reader/exprseq_xs.hpp b/include/xo/reader/exprseq_xs.hpp index 28a17088..261cbd5a 100644 --- a/include/xo/reader/exprseq_xs.hpp +++ b/include/xo/reader/exprseq_xs.hpp @@ -15,6 +15,11 @@ namespace xo { * **/ class exprseq_xs : public exprstate { + public: + exprseq_xs(); + + static std::unique_ptr expect_toplevel_expression_sequence(); + public: // ----- token input methods ----- diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index c775a5b9..7b590f48 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -70,9 +70,11 @@ namespace xo { {} virtual ~exprstate() = default; +#ifdef RELOCATED static std::unique_ptr expect_toplevel_expression_sequence() { return std::make_unique(exprstate(exprstatetype::expect_toplevel_expression_sequence)); } +#endif static std::unique_ptr expect_rhs_expression() { return std::make_unique(exprstate(exprstatetype::expect_rhs_expression)); } @@ -85,10 +87,12 @@ namespace xo { exprstatetype exs_type() const { return exs_type_; } +#ifdef OBSOLETE /** true iff this parsing state admits a 'def' keyword * as next token **/ virtual bool admits_definition() const; +#endif /** true iff this parsing state admits a symbol as next token **/ virtual bool admits_symbol() const; /** true iff this parsing state admits a colon as next token **/ diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 5992837a..670a62c3 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -31,7 +31,9 @@ namespace xo { static std::unique_ptr lparen_0(); +#ifdef OBSOLETE virtual bool admits_definition() const override; +#endif virtual bool admits_symbol() const override; virtual bool admits_colon() const override; virtual bool admits_semicolon() const override; diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 0a0516cf..9df42717 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -22,7 +22,9 @@ namespace xo { static std::unique_ptr make(rp valex); +#ifdef OBSOLETE virtual bool admits_definition() const override; +#endif virtual bool admits_symbol() const override; virtual bool admits_colon() const override; virtual bool admits_semicolon() const override; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 95fde120..3d85c860 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -15,6 +15,7 @@ namespace xo { def_expr_{std::move(def_expr)} {} +#ifdef OBSOLETE bool define_xs::admits_definition() const { @@ -41,6 +42,7 @@ namespace xo { return false; } +#endif bool define_xs::admits_symbol() const { diff --git a/src/reader/exprseq_xs.cpp b/src/reader/exprseq_xs.cpp index 012e9b2e..e0a5af6f 100644 --- a/src/reader/exprseq_xs.cpp +++ b/src/reader/exprseq_xs.cpp @@ -5,6 +5,17 @@ namespace xo { namespace scm { + std::unique_ptr + exprseq_xs::expect_toplevel_expression_sequence() + { + return std::make_unique(exprseq_xs()); + } + + exprseq_xs::exprseq_xs() + : exprstate(exprstatetype::expect_toplevel_expression_sequence) + { + } + void exprseq_xs::on_def_token(const token_type & /*tk*/, exprstatestack * p_stack) @@ -29,5 +40,4 @@ namespace xo { } /*namespace scm*/ } /*namespace xo*/ - /* end exprseq_xs.cpp */ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 6116b349..b04aa227 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -323,8 +323,11 @@ namespace xo { void exprstate::on_def_token(const token_type & tk, - exprstatestack * p_stack) + exprstatestack * /*p_stack*/) { + this->illegal_input_error("exprstate::on_def_token", tk); + +#ifdef OBSOLETE constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -347,6 +350,7 @@ namespace xo { * def pi : f64 = 3.14159265 * def sq(x : f64) -> f64 { (x * x) } */ +#endif } void diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 6d464178..0911928d 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -14,8 +14,10 @@ namespace xo { return std::make_unique(paren_xs()); } +#ifdef OBSOLETE bool paren_xs::admits_definition() const { return false; } +#endif bool paren_xs::admits_symbol() const { return true; } diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index f056eeac..5ac71dd0 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -5,6 +5,7 @@ #include "parser.hpp" #include "define_xs.hpp" +#include "exprseq_xs.hpp" #include "xo/expression/DefineExpr.hpp" #include "xo/expression/Constant.hpp" #include "xo/expression/ConvertExpr.hpp" @@ -30,7 +31,7 @@ namespace xo { void parser::begin_translation_unit() { xs_stack_.push_exprstate - (exprstate::expect_toplevel_expression_sequence()); + (exprseq_xs::expect_toplevel_expression_sequence()); } rp diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index a355229e..baf4df55 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -14,8 +14,10 @@ namespace xo { gen_expr_{std::move(valex)} {} +#ifdef OBSOLETE bool progress_xs::admits_definition() const { return false; } +#endif bool progress_xs::admits_symbol() const { return false; } From 6473ab6e26f0efd71e360910d9f57765f9dcbfc5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 19:45:19 -0400 Subject: [PATCH 1357/2524] xo-reader: tidy: kill RELOCATED + OBSOLETE code --- include/xo/reader/exprstate.hpp | 11 ------- src/reader/exprstate.cpp | 56 --------------------------------- 2 files changed, 67 deletions(-) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 7b590f48..41d4da81 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -70,11 +70,6 @@ namespace xo { {} virtual ~exprstate() = default; -#ifdef RELOCATED - static std::unique_ptr expect_toplevel_expression_sequence() { - return std::make_unique(exprstate(exprstatetype::expect_toplevel_expression_sequence)); - } -#endif static std::unique_ptr expect_rhs_expression() { return std::make_unique(exprstate(exprstatetype::expect_rhs_expression)); } @@ -87,12 +82,6 @@ namespace xo { exprstatetype exs_type() const { return exs_type_; } -#ifdef OBSOLETE - /** true iff this parsing state admits a 'def' keyword - * as next token - **/ - virtual bool admits_definition() const; -#endif /** true iff this parsing state admits a symbol as next token **/ virtual bool admits_symbol() const; /** true iff this parsing state admits a colon as next token **/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index b04aa227..beab670a 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -41,37 +41,6 @@ namespace xo { return "???"; } -#ifdef OBSOLETE - bool - exprstate::admits_definition() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - return true; - - case exprstatetype::defexpr: - case exprstatetype::parenexpr: - /* unreachable */ - assert(false); - return false; - case exprstatetype::expect_rhs_expression: - return false; - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - return false; - case exprstatetype::expr_progress: - /* unreachable */ - assert(false); - return false; - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - return false; - } - - return false; - } -#endif - bool exprstate::admits_symbol() const { switch (exs_type_) { @@ -326,31 +295,6 @@ namespace xo { exprstatestack * /*p_stack*/) { this->illegal_input_error("exprstate::on_def_token", tk); - -#ifdef OBSOLETE - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - - constexpr const char * c_self_name = "exprstate::on_def_token"; - - /* lots of illegal states */ - if (!this->admits_definition()) - { - this->illegal_input_error(c_self_name, tk); - } - - p_stack->push_exprstate(define_xs::def_0()); - - /* todo: replace: - * expect_symbol_or_function_signature() - */ - p_stack->push_exprstate(exprstate::expect_symbol()); - - /* keyword 'def' introduces a definition: - * def pi : f64 = 3.14159265 - * def sq(x : f64) -> f64 { (x * x) } - */ -#endif } void From 30cfe0c918256c5cb12b4d47e5b32eb7061537ae Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 19:52:30 -0400 Subject: [PATCH 1358/2524] xo-reader: simplify exprseq_xs::on_symbol_token() --- include/xo/reader/exprseq_xs.hpp | 6 ++++++ src/reader/exprseq_xs.cpp | 10 ++++++++++ src/reader/exprstate.cpp | 9 +-------- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/include/xo/reader/exprseq_xs.hpp b/include/xo/reader/exprseq_xs.hpp index 261cbd5a..861a6321 100644 --- a/include/xo/reader/exprseq_xs.hpp +++ b/include/xo/reader/exprseq_xs.hpp @@ -25,6 +25,12 @@ namespace xo { virtual void on_def_token(const token_type & tk, exprstatestack * p_stack) override; + virtual void on_symbol_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; + + // ----- victory methods ----- + }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprseq_xs.cpp b/src/reader/exprseq_xs.cpp index e0a5af6f..a127cdc8 100644 --- a/src/reader/exprseq_xs.cpp +++ b/src/reader/exprseq_xs.cpp @@ -37,6 +37,16 @@ namespace xo { * def sq(x : f64) -> f64 { (x * x) } */ } + + void + exprseq_xs::on_symbol_token(const token_type & tk, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + constexpr const char * c_self_name = "exprseq_xs::on_symbol_token"; + + this->illegal_input_error(c_self_name, tk); + } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index beab670a..3e3ae9ec 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -315,13 +315,6 @@ namespace xo { switch (this->exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: - throw std::runtime_error - (tostr(c_self_name, - ": unexpected symbol-token at top-level", - " (expecting decl|def)", - xtag("symbol", tk))); - break; - case exprstatetype::defexpr: case exprstatetype::parenexpr: /* unreachable - redirects to define_xs */ @@ -409,7 +402,7 @@ namespace xo { assert(false); return; } - } /*on_symbol*/ + } void exprstate::on_typedescr(TypeDescr /*td*/, From cf3448bc8249bbc1ad9516c0621723791439f040 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 19:56:47 -0400 Subject: [PATCH 1359/2524] xo-reader: + exprseq_xs.on_typedef() + simplie tl exprseq case --- include/xo/reader/exprseq_xs.hpp | 4 ++++ src/reader/exprseq_xs.cpp | 10 ++++++++++ src/reader/exprstate.cpp | 1 - 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/include/xo/reader/exprseq_xs.hpp b/include/xo/reader/exprseq_xs.hpp index 861a6321..6ea8ff21 100644 --- a/include/xo/reader/exprseq_xs.hpp +++ b/include/xo/reader/exprseq_xs.hpp @@ -31,6 +31,10 @@ namespace xo { // ----- victory methods ----- + virtual void on_typedescr(TypeDescr /*td*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) override; + }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprseq_xs.cpp b/src/reader/exprseq_xs.cpp index a127cdc8..48c69fbd 100644 --- a/src/reader/exprseq_xs.cpp +++ b/src/reader/exprseq_xs.cpp @@ -47,6 +47,16 @@ namespace xo { this->illegal_input_error(c_self_name, tk); } + + void + exprseq_xs::on_typedescr(TypeDescr /*td*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + /* unreachable - typedescr should never get delivered to exprseq */ + assert(false); + return; + } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 3e3ae9ec..7fa30923 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -413,7 +413,6 @@ namespace xo { switch (this->exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::defexpr: case exprstatetype::parenexpr: /* unreachable - redirects to define_xs */ From 372a86485f5fa9048160b48af69f4e8ea4e39983 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 20:01:22 -0400 Subject: [PATCH 1360/2524] xo-reader: + exprseq_xs.on_expr() --- include/xo/reader/exprseq_xs.hpp | 9 ++++++--- src/reader/exprseq_xs.cpp | 15 +++++++++++++++ src/reader/exprstate.cpp | 8 -------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/include/xo/reader/exprseq_xs.hpp b/include/xo/reader/exprseq_xs.hpp index 6ea8ff21..d6afffe8 100644 --- a/include/xo/reader/exprseq_xs.hpp +++ b/include/xo/reader/exprseq_xs.hpp @@ -31,9 +31,12 @@ namespace xo { // ----- victory methods ----- - virtual void on_typedescr(TypeDescr /*td*/, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) override; + virtual void on_typedescr(TypeDescr td, + exprstatestack * p_stack, + rp * p_emit_expr) override; + virtual void on_expr(ref::brw expr, + exprstatestack * p_stack, + rp * p_emit_expr) override; }; } /*namespace scm*/ diff --git a/src/reader/exprseq_xs.cpp b/src/reader/exprseq_xs.cpp index 48c69fbd..502f129f 100644 --- a/src/reader/exprseq_xs.cpp +++ b/src/reader/exprseq_xs.cpp @@ -57,6 +57,21 @@ namespace xo { assert(false); return; } + + void + exprseq_xs::on_expr(ref::brw expr, + exprstatestack * /*p_stack*/, + rp * p_emit_expr) + { + /* toplevel expression sequence accepts an + * arbitrary number of expressions. + * + * parser::include_token() returns + */ + + *p_emit_expr = expr.promote(); + } /*on_expr*/ + } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 7fa30923..052488bf 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -680,14 +680,6 @@ namespace xo { switch (this->exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: - /* toplevel expression sequence accepts an - * arbitrary number of expressions. - * - * parser::include_token() returns - */ - - *p_emit_expr = expr.promote(); - return; case exprstatetype::defexpr: case exprstatetype::parenexpr: /* unreachable. redirects to define_xs::on_expr() etc */ From e2f9fbb9a47c27802aeba68814851c175953418f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 20:04:16 -0400 Subject: [PATCH 1361/2524] xo-reader: streamline tl expr seq --- src/reader/exprstate.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 052488bf..d9ea94b6 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -82,7 +82,6 @@ namespace xo { exprstate::admits_colon() const { switch (exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::defexpr: case exprstatetype::parenexpr: /* unreachable -- redirects to define_xs::admits_colon() etc */ @@ -137,7 +136,6 @@ namespace xo { exprstate::admits_singleassign() const { switch (exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: - /* * def foo = 1 ; * def foo : f64 = 1 ; @@ -262,7 +260,6 @@ namespace xo { exprstate::admits_f64() const { switch (exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::defexpr: case exprstatetype::parenexpr: /* unreachable - redirects to define_xs */ @@ -723,15 +720,6 @@ namespace xo { { switch(this->exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: - /* toplevel expression sequence accepts an - * arbitrary number of expressions. - * - * parser::include_token() returns - */ - - /* NOT IMPLEMENTED */ - assert(false); - return; case exprstatetype::defexpr: case exprstatetype::parenexpr: /* unreachable - redirects to define_xs etc */ From f0f4c57362538fb3567f8b4d922f084193720318 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 20:09:12 -0400 Subject: [PATCH 1362/2524] xo-reader: streamline: drop degenerate switch stmts --- src/reader/exprstate.cpp | 60 +++++----------------------------------- 1 file changed, 7 insertions(+), 53 deletions(-) diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index d9ea94b6..6e876bac 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -408,34 +408,9 @@ namespace xo { { /* returning type description to something that wants it */ - switch (this->exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::defexpr: - case exprstatetype::parenexpr: - /* unreachable - redirects to define_xs */ - assert(false); - return; - - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_type: - case exprstatetype::expect_symbol: - /* unreachable - * (this exprstate issues pop instruction from exprstate::on_input() - */ - assert(false); - return; - - case exprstatetype::expr_progress: - /* unreachable */ - assert(false); - return; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - assert(false); - return; - } + /* unreachable - implement in derived class */ + assert(false); + return; } void @@ -718,31 +693,10 @@ namespace xo { exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { - switch(this->exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::defexpr: - case exprstatetype::parenexpr: - /* unreachable - redirects to define_xs etc */ - assert(false); - return; - - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_type: - case exprstatetype::expect_symbol: - /* unreachable - * (this exprstate issues pop instruction from exprstate::on_input() - */ - assert(false); - return; - case exprstatetype::expr_progress: - assert(false); - return; - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - assert(false); - return; - } + /* unreachable - derived class that can receive + * will override this method + */ + assert(false); } void From 5a4923e5cf6bba7f213be5c596aa76b60ae483a9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 20:20:57 -0400 Subject: [PATCH 1363/2524] xo-reader: refactor: + expect_expr_xs cls [wip] --- include/xo/reader/expect_expr_xs.hpp | 29 +++++++++++++++++++++++++ include/xo/reader/exprstate.hpp | 2 ++ src/reader/CMakeLists.txt | 3 ++- src/reader/define_xs.cpp | 32 ++-------------------------- src/reader/expect_expr_xs.cpp | 24 +++++++++++++++++++++ src/reader/exprstate.cpp | 5 +++-- 6 files changed, 62 insertions(+), 33 deletions(-) create mode 100644 include/xo/reader/expect_expr_xs.hpp create mode 100644 src/reader/expect_expr_xs.cpp diff --git a/include/xo/reader/expect_expr_xs.hpp b/include/xo/reader/expect_expr_xs.hpp new file mode 100644 index 00000000..6e039c79 --- /dev/null +++ b/include/xo/reader/expect_expr_xs.hpp @@ -0,0 +1,29 @@ +/* file expect_expr_xs.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "exprstate.hpp" + +namespace xo { + namespace scm { + + /** @class expect_expr_xs + * @brief state machine to expect + capture an expression + **/ + class expect_expr_xs : public exprstate { + public: + expect_expr_xs(); + + static std::unique_ptr expect_rhs_expression(); + + private: + }; + + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end expect_expr_xs.hpp */ diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 41d4da81..1a0219e6 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -70,9 +70,11 @@ namespace xo { {} virtual ~exprstate() = default; +#ifdef RELOCATED static std::unique_ptr expect_rhs_expression() { return std::make_unique(exprstate(exprstatetype::expect_rhs_expression)); } +#endif static std::unique_ptr expect_symbol() { return std::make_unique(exprstate(exprstatetype::expect_symbol)); } diff --git a/src/reader/CMakeLists.txt b/src/reader/CMakeLists.txt index e75e248a..77a06a63 100644 --- a/src/reader/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -8,7 +8,8 @@ set(SELF_SRCS define_xs.cpp progress_xs.cpp paren_xs.cpp - exprseq_xs.cpp) + exprseq_xs.cpp + expect_expr_xs.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_dependency(${SELF_LIB} xo_expression) diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 3d85c860..5dc5c760 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -1,6 +1,7 @@ /* @file define_xs.cpp */ #include "define_xs.hpp" +#include "expect_expr_xs.hpp" namespace xo { namespace scm { @@ -15,35 +16,6 @@ namespace xo { def_expr_{std::move(def_expr)} {} -#ifdef OBSOLETE - bool - define_xs::admits_definition() const - { - switch (defxs_type_) { - - case defexprstatetype::def_0: - case defexprstatetype::def_1: - case defexprstatetype::def_2: - case defexprstatetype::def_3: - case defexprstatetype::def_4: - case defexprstatetype::def_5: - /* note for def_4: - * rhs could certainly be a function body that contains - * nested defines; but then immediately-enclosing-exprstate - * would be a block - */ - return false; - case defexprstatetype::invalid: - case defexprstatetype::n_defexprstatetype: - /* unreachable */ - assert(false); - return false; - } - - return false; - } -#endif - bool define_xs::admits_symbol() const { switch (defxs_type_) { @@ -401,7 +373,7 @@ namespace xo { { this->defxs_type_ = defexprstatetype::def_4; - p_stack->push_exprstate(exprstate::expect_rhs_expression()); + p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); } else { assert(false); } diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp new file mode 100644 index 00000000..ddc75aa6 --- /dev/null +++ b/src/reader/expect_expr_xs.cpp @@ -0,0 +1,24 @@ +/* file expect_expr_xs.cpp + * + * author: Roland Conybeare + */ + +#include "expect_expr_xs.hpp" + +namespace xo { + namespace scm { + + std::unique_ptr + expect_expr_xs::expect_rhs_expression() { + return std::make_unique(expect_expr_xs()); + + } + + expect_expr_xs::expect_expr_xs() + : exprstate(exprstatetype::expect_rhs_expression) + {} + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end expect_expr_xs.cpp */ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 6e876bac..88154353 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -4,6 +4,7 @@ #include "define_xs.hpp" #include "progress_xs.hpp" #include "paren_xs.hpp" +#include "expect_expr_xs.hpp" //#include "xo/expression/DefineExpr.hpp" #include "xo/expression/Constant.hpp" //#include "xo/expression/ConvertExpr.hpp" @@ -491,7 +492,7 @@ namespace xo { if (this->exs_type_ == exprstatetype::expect_rhs_expression) { /* push lparen_0 to remember to look for subsequent rightparen. */ p_stack->push_exprstate(paren_xs::lparen_0()); - p_stack->push_exprstate(exprstate::expect_rhs_expression()); + p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); } } @@ -660,7 +661,7 @@ namespace xo { case exprstatetype::expect_rhs_expression: { - std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + std::unique_ptr self = p_stack->pop_exprstate(); p_stack->top_exprstate().on_expr(expr, p_stack, From b8b9efd6339266b3c64fcca7a9f9f3b2277cbb56 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 20:28:58 -0400 Subject: [PATCH 1364/2524] xo-reader: simplify f64 token handling --- include/xo/reader/expect_expr_xs.hpp | 4 ++++ include/xo/reader/exprstate.hpp | 2 ++ include/xo/reader/paren_xs.hpp | 6 ++---- include/xo/reader/progress_xs.hpp | 6 ++---- src/reader/define_xs.cpp | 7 +------ src/reader/expect_expr_xs.cpp | 24 ++++++++++++++++++++++++ src/reader/exprstate.cpp | 27 +++++---------------------- 7 files changed, 40 insertions(+), 36 deletions(-) diff --git a/include/xo/reader/expect_expr_xs.hpp b/include/xo/reader/expect_expr_xs.hpp index 6e039c79..d826b5ae 100644 --- a/include/xo/reader/expect_expr_xs.hpp +++ b/include/xo/reader/expect_expr_xs.hpp @@ -19,6 +19,10 @@ namespace xo { static std::unique_ptr expect_rhs_expression(); + virtual void on_f64_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; + private: }; diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 1a0219e6..2ac8cf67 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -96,8 +96,10 @@ namespace xo { virtual bool admits_leftparen() const; /** truee iff this parsing state admits a rightparen ')' as next token **/ virtual bool admits_rightparen() const; +#ifdef OBSOLETE /** true iff this parsing state admits a 64-bit floating point literal token **/ virtual bool admits_f64() const; +#endif /** update exprstate in response to incoming token @p tk, * forward instructions to parent parser diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 670a62c3..31e3e45c 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -31,16 +31,14 @@ namespace xo { static std::unique_ptr lparen_0(); -#ifdef OBSOLETE - virtual bool admits_definition() const override; -#endif + bool admits_f64() const; + virtual bool admits_symbol() const override; virtual bool admits_colon() const override; virtual bool admits_semicolon() const override; virtual bool admits_singleassign() const override; virtual bool admits_leftparen() const override; virtual bool admits_rightparen() const override; - virtual bool admits_f64() const override; virtual void on_expr(ref::brw expr, exprstatestack * p_stack, diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 9df42717..9131b048 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -22,16 +22,14 @@ namespace xo { static std::unique_ptr make(rp valex); -#ifdef OBSOLETE - virtual bool admits_definition() const override; -#endif + bool admits_f64() const; + virtual bool admits_symbol() const override; virtual bool admits_colon() const override; virtual bool admits_semicolon() const override; virtual bool admits_singleassign() const override; virtual bool admits_leftparen() const override; virtual bool admits_rightparen() const override; - virtual bool admits_f64() const override; virtual void on_expr(ref::brw expr, exprstatestack * p_stack, diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 5dc5c760..ad6c994f 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -425,12 +425,7 @@ namespace xo { constexpr const char * self_name = "exprstate::on_f64"; - if (!this->admits_f64()) - { - this->illegal_input_error(self_name, tk); - } - - assert(false); + this->illegal_input_error(self_name, tk); } void diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index ddc75aa6..78a69be2 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -4,8 +4,12 @@ */ #include "expect_expr_xs.hpp" +#include "progress_xs.hpp" +#include "xo/expression/Constant.hpp" namespace xo { + using xo::ast::Constant; + namespace scm { std::unique_ptr @@ -18,6 +22,26 @@ namespace xo { : exprstate(exprstatetype::expect_rhs_expression) {} + void + expect_expr_xs::on_f64_token(const token_type & tk, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + //constexpr const char * self_name = "exprstate::on_f64_token"; + + /* e.g. + * def pi = 3.14159265; + * \---tk---/ + */ + p_stack->push_exprstate + (progress_xs::make + (Constant::make(tk.f64_value()))); + } + + } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 88154353..8161f62e 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -257,19 +257,18 @@ namespace xo { return false; } +#ifdef OBSOLETE bool exprstate::admits_f64() const { switch (exs_type_) { case exprstatetype::expect_toplevel_expression_sequence: case exprstatetype::defexpr: case exprstatetype::parenexpr: + case exprstatetype::expect_rhs_expression: /* unreachable - redirects to define_xs */ assert(false); return false; - case exprstatetype::expect_rhs_expression: - return true; - case exprstatetype::expect_symbol: case exprstatetype::expect_type: return false; @@ -287,6 +286,7 @@ namespace xo { return false; } +#endif void exprstate::on_def_token(const token_type & tk, @@ -522,7 +522,7 @@ namespace xo { void exprstate::on_f64_token(const token_type & tk, - exprstatestack * p_stack, + exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; @@ -530,24 +530,7 @@ namespace xo { constexpr const char * self_name = "exprstate::on_f64"; - if (!this->admits_f64()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected floating-point literal for parsing state", - xtag("state", *this))); - } - - if (this->exs_type_ == exprstatetype::expect_rhs_expression) { - /* e.g. - * def pi = 3.14159265; - * \---tk---/ - */ - p_stack->push_exprstate - (progress_xs::make - (Constant::make(tk.f64_value()))); - } else { - assert(false); - } + this->illegal_input_error(self_name, tk); } void From 6b9503ef7668f2eb34cf77d7d33728bb9201df02 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 20:33:28 -0400 Subject: [PATCH 1365/2524] xo-reader: simplify paren handling for expressions --- include/xo/reader/expect_expr_xs.hpp | 3 +++ include/xo/reader/exprstate.hpp | 9 ------- src/reader/expect_expr_xs.cpp | 16 ++++++++++++ src/reader/exprstate.cpp | 38 ++-------------------------- 4 files changed, 21 insertions(+), 45 deletions(-) diff --git a/include/xo/reader/expect_expr_xs.hpp b/include/xo/reader/expect_expr_xs.hpp index d826b5ae..a7cc997f 100644 --- a/include/xo/reader/expect_expr_xs.hpp +++ b/include/xo/reader/expect_expr_xs.hpp @@ -19,6 +19,9 @@ namespace xo { static std::unique_ptr expect_rhs_expression(); + virtual void on_leftparen_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; virtual void on_f64_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) override; diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 2ac8cf67..0997792e 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -70,11 +70,6 @@ namespace xo { {} virtual ~exprstate() = default; -#ifdef RELOCATED - static std::unique_ptr expect_rhs_expression() { - return std::make_unique(exprstate(exprstatetype::expect_rhs_expression)); - } -#endif static std::unique_ptr expect_symbol() { return std::make_unique(exprstate(exprstatetype::expect_symbol)); } @@ -96,10 +91,6 @@ namespace xo { virtual bool admits_leftparen() const; /** truee iff this parsing state admits a rightparen ')' as next token **/ virtual bool admits_rightparen() const; -#ifdef OBSOLETE - /** true iff this parsing state admits a 64-bit floating point literal token **/ - virtual bool admits_f64() const; -#endif /** update exprstate in response to incoming token @p tk, * forward instructions to parent parser diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index 78a69be2..be19b8bc 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -4,6 +4,7 @@ */ #include "expect_expr_xs.hpp" +#include "paren_xs.hpp" #include "progress_xs.hpp" #include "xo/expression/Constant.hpp" @@ -22,6 +23,21 @@ namespace xo { : exprstate(exprstatetype::expect_rhs_expression) {} + void + expect_expr_xs::on_leftparen_token(const token_type & /*tk*/, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + //constexpr const char * self_name = "exprstate::on_leftparen"; + + /* push lparen_0 to remember to look for subsequent rightparen. */ + p_stack->push_exprstate(paren_xs::lparen_0()); + p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + } + void expect_expr_xs::on_f64_token(const token_type & tk, exprstatestack * p_stack, diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 8161f62e..b013feb1 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -196,14 +196,11 @@ namespace xo { case exprstatetype::defexpr: case exprstatetype::parenexpr: - /* unreachable - redirects to define_xs */ + case exprstatetype::expect_rhs_expression: + /* unreachable - redirects to define_xs etc */ assert(false); return false; - case exprstatetype::expect_rhs_expression: - /* can always begin non-toplevel expression with '(' */ - return true; - case exprstatetype::expect_type: return false; @@ -257,37 +254,6 @@ namespace xo { return false; } -#ifdef OBSOLETE - bool - exprstate::admits_f64() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::defexpr: - case exprstatetype::parenexpr: - case exprstatetype::expect_rhs_expression: - /* unreachable - redirects to define_xs */ - assert(false); - return false; - - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - return false; - - case exprstatetype::expr_progress: - /* unreachable */ - assert(false); - return false; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - return false; - } - - return false; - } -#endif - void exprstate::on_def_token(const token_type & tk, exprstatestack * /*p_stack*/) From 538c9d90e5f69435882c836d86e3d1cfa7d89210 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 20:40:37 -0400 Subject: [PATCH 1366/2524] xo-reader: simplify paren handling logic --- include/xo/reader/define_xs.hpp | 6 ++++-- include/xo/reader/exprstate.hpp | 2 ++ include/xo/reader/paren_xs.hpp | 2 ++ include/xo/reader/progress_xs.hpp | 2 ++ src/reader/define_xs.cpp | 9 +++++---- src/reader/exprstate.cpp | 19 +++++-------------- src/reader/paren_xs.cpp | 7 ++----- src/reader/progress_xs.cpp | 9 --------- 8 files changed, 22 insertions(+), 34 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index dafafed4..34670230 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -43,13 +43,13 @@ namespace xo { defexprstatetype defxs_type() const { return defxs_type_; } #ifdef OBSOLETE - virtual bool admits_definition() const override; + bool admits_leftparen() const; #endif + virtual bool admits_symbol() const override; virtual bool admits_colon() const override; virtual bool admits_semicolon() const override; virtual bool admits_singleassign() const override; - virtual bool admits_leftparen() const override; virtual bool admits_rightparen() const override; // virtual void on_f64(..) override @@ -69,9 +69,11 @@ namespace xo { rp * /*p_emit_expr*/) override; virtual void on_singleassign_token(const token_type & tk, exprstatestack * p_stack) override; +#ifdef OBSOLETE virtual void on_leftparen_token(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; +#endif virtual void on_rightparen_token(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 0997792e..28ba4694 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -87,8 +87,10 @@ namespace xo { virtual bool admits_semicolon() const; /** true iff this parsing state admits a singleassign '=' as next token **/ virtual bool admits_singleassign() const; +#ifdef OBSOLETE /** true iff this parsing state admits a leftparen '(' as next token **/ virtual bool admits_leftparen() const; +#endif /** truee iff this parsing state admits a rightparen ')' as next token **/ virtual bool admits_rightparen() const; diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 31e3e45c..22b72f8b 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -37,7 +37,9 @@ namespace xo { virtual bool admits_colon() const override; virtual bool admits_semicolon() const override; virtual bool admits_singleassign() const override; +#ifdef OBSOLETE virtual bool admits_leftparen() const override; +#endif virtual bool admits_rightparen() const override; virtual void on_expr(ref::brw expr, diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 9131b048..83a2ce65 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -28,7 +28,9 @@ namespace xo { virtual bool admits_colon() const override; virtual bool admits_semicolon() const override; virtual bool admits_singleassign() const override; +#ifdef OBSOLETE virtual bool admits_leftparen() const override; +#endif virtual bool admits_rightparen() const override; virtual void on_expr(ref::brw expr, diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index ad6c994f..ffa8a094 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -131,6 +131,7 @@ namespace xo { return false; } +#ifdef OBSOLETE bool define_xs::admits_leftparen() const { switch (defxs_type_) { @@ -163,6 +164,7 @@ namespace xo { return false; } +#endif bool define_xs::admits_rightparen() const { @@ -379,6 +381,7 @@ namespace xo { } } +#ifdef OBSOLETE void define_xs::on_leftparen_token(const token_type & tk, exprstatestack * /*p_stack*/, @@ -389,13 +392,11 @@ namespace xo { constexpr const char * self_name = "exprstate::on_leftparen"; - if (!this->admits_leftparen()) - { - this->illegal_input_error(self_name, tk); - } + this->illegal_input_error(self_name, tk); assert(false); /* inserting this during refactor...? */ } +#endif void define_xs::on_rightparen_token(const token_type & tk, diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index b013feb1..1fd4d08a 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -179,6 +179,7 @@ namespace xo { return false; } +#ifdef OBSOLETE bool exprstate::admits_leftparen() const { switch (exs_type_) { @@ -221,6 +222,7 @@ namespace xo { return false; } +#endif bool exprstate::admits_rightparen() const { @@ -439,8 +441,8 @@ namespace xo { } void - exprstate::on_leftparen_token(const token_type & /*tk*/, - exprstatestack * p_stack, + exprstate::on_leftparen_token(const token_type & tk, + exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; @@ -448,18 +450,7 @@ namespace xo { constexpr const char * self_name = "exprstate::on_leftparen"; - if (!this->admits_leftparen()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected leftparen '(' for parsing state", - xtag("state", *this))); - } - - if (this->exs_type_ == exprstatetype::expect_rhs_expression) { - /* push lparen_0 to remember to look for subsequent rightparen. */ - p_stack->push_exprstate(paren_xs::lparen_0()); - p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); - } + this->illegal_input_error(self_name, tk); } void diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 0911928d..2fcf427b 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -14,11 +14,6 @@ namespace xo { return std::make_unique(paren_xs()); } -#ifdef OBSOLETE - bool - paren_xs::admits_definition() const { return false; } -#endif - bool paren_xs::admits_symbol() const { return true; } @@ -31,8 +26,10 @@ namespace xo { bool paren_xs::admits_singleassign() const { return false; } +#ifdef OBSOLETE bool paren_xs::admits_leftparen() const { /*unreachable*/ return false; } +#endif bool paren_xs::admits_rightparen() const { diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index baf4df55..f5ba36ee 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -14,11 +14,6 @@ namespace xo { gen_expr_{std::move(valex)} {} -#ifdef OBSOLETE - bool - progress_xs::admits_definition() const { return false; } -#endif - bool progress_xs::admits_symbol() const { return false; } @@ -31,10 +26,6 @@ namespace xo { bool progress_xs::admits_singleassign() const { return false; } - /* todo: will parse as function call */ - bool - progress_xs::admits_leftparen() const { return false; } - bool progress_xs::admits_rightparen() const { /* satisfies expression form */ From fb30451684070bca2a54c4524fbe7ebf981984fb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 Aug 2024 20:45:06 -0400 Subject: [PATCH 1367/2524] xo-reader: simplify rightparen logic --- include/xo/reader/define_xs.hpp | 10 +---- include/xo/reader/exprstate.hpp | 2 +- include/xo/reader/paren_xs.hpp | 2 +- include/xo/reader/progress_xs.hpp | 2 +- src/reader/exprstate.cpp | 64 +++---------------------------- src/reader/progress_xs.cpp | 2 + 6 files changed, 11 insertions(+), 71 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 34670230..8c9ef9c1 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -42,15 +42,12 @@ namespace xo { defexprstatetype defxs_type() const { return defxs_type_; } -#ifdef OBSOLETE - bool admits_leftparen() const; -#endif + bool admits_rightparen() const; virtual bool admits_symbol() const override; virtual bool admits_colon() const override; virtual bool admits_semicolon() const override; virtual bool admits_singleassign() const override; - virtual bool admits_rightparen() const override; // virtual void on_f64(..) override virtual void on_expr(ref::brw expr, @@ -69,11 +66,6 @@ namespace xo { rp * /*p_emit_expr*/) override; virtual void on_singleassign_token(const token_type & tk, exprstatestack * p_stack) override; -#ifdef OBSOLETE - virtual void on_leftparen_token(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; -#endif virtual void on_rightparen_token(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 28ba4694..61ba3319 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -90,9 +90,9 @@ namespace xo { #ifdef OBSOLETE /** true iff this parsing state admits a leftparen '(' as next token **/ virtual bool admits_leftparen() const; -#endif /** truee iff this parsing state admits a rightparen ')' as next token **/ virtual bool admits_rightparen() const; +#endif /** update exprstate in response to incoming token @p tk, * forward instructions to parent parser diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 22b72f8b..d00dc384 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -32,6 +32,7 @@ namespace xo { static std::unique_ptr lparen_0(); bool admits_f64() const; + bool admits_rightparen() const; virtual bool admits_symbol() const override; virtual bool admits_colon() const override; @@ -40,7 +41,6 @@ namespace xo { #ifdef OBSOLETE virtual bool admits_leftparen() const override; #endif - virtual bool admits_rightparen() const override; virtual void on_expr(ref::brw expr, exprstatestack * p_stack, diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 83a2ce65..d4a0c190 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -30,8 +30,8 @@ namespace xo { virtual bool admits_singleassign() const override; #ifdef OBSOLETE virtual bool admits_leftparen() const override; -#endif virtual bool admits_rightparen() const override; +#endif virtual void on_expr(ref::brw expr, exprstatestack * p_stack, diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 1fd4d08a..31553c1c 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -180,50 +180,6 @@ namespace xo { } #ifdef OBSOLETE - bool - exprstate::admits_leftparen() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - /* input like - * (function(blah...)) - * not allowed at toplevel; - * creates ambiguity e.g. consider - * x := foo - * (bar) - * - * is rhs 'foo' or 'foo(bar)' - */ - return false; - - case exprstatetype::defexpr: - case exprstatetype::parenexpr: - case exprstatetype::expect_rhs_expression: - /* unreachable - redirects to define_xs etc */ - assert(false); - return false; - - case exprstatetype::expect_type: - return false; - - case exprstatetype::expect_symbol: - return false; - - case exprstatetype::expr_progress: - /* unreachable */ - assert(false); - return false; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - assert(false); - return false; - } - - return false; - } -#endif - bool exprstate::admits_rightparen() const { switch (exs_type_) { @@ -255,6 +211,7 @@ namespace xo { return false; } +#endif void exprstate::on_def_token(const token_type & tk, @@ -428,7 +385,7 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - constexpr const char * self_name = "exprstate::on_singleassign"; + constexpr const char * self_name = "exprstate::on_singleassign_token"; if (!this->admits_singleassign()) { @@ -448,13 +405,13 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - constexpr const char * self_name = "exprstate::on_leftparen"; + constexpr const char * self_name = "exprstate::on_leftparen_token"; this->illegal_input_error(self_name, tk); } void - exprstate::on_rightparen_token(const token_type & /*tk*/, + exprstate::on_rightparen_token(const token_type & tk, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { @@ -463,18 +420,7 @@ namespace xo { constexpr const char * self_name = "exprstate::on_rightparen"; - if (!this->admits_rightparen()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected rightparen ')' for parsing state", - xtag("state", *this))); - } - - if (this->exs_type_ == exprstatetype::expr_progress - || this->exs_type_ == exprstatetype::parenexpr) { - /* unreachable -- see progress_xs::on_rightparen() etc */ - assert(false); - } + this->illegal_input_error(self_name, tk); } void diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index f5ba36ee..f518374a 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -26,11 +26,13 @@ namespace xo { bool progress_xs::admits_singleassign() const { return false; } +#ifdef OBSOLETE bool progress_xs::admits_rightparen() const { /* satisfies expression form */ return true; } +#endif bool progress_xs::admits_f64() const { return false; } From d1d72f9fc9d81c75dede4fe4402684584de27c00 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 Aug 2024 15:56:48 -0500 Subject: [PATCH 1368/2524] xo-reader: streamline: simplify expect_expr_xs --- include/xo/reader/expect_expr_xs.hpp | 10 +++ include/xo/reader/exprstate.hpp | 6 -- src/reader/expect_expr_xs.cpp | 53 +++++++++++++ src/reader/exprstate.cpp | 112 ++------------------------- 4 files changed, 69 insertions(+), 112 deletions(-) diff --git a/include/xo/reader/expect_expr_xs.hpp b/include/xo/reader/expect_expr_xs.hpp index a7cc997f..fa6c4630 100644 --- a/include/xo/reader/expect_expr_xs.hpp +++ b/include/xo/reader/expect_expr_xs.hpp @@ -22,10 +22,20 @@ namespace xo { virtual void on_leftparen_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) override; + + virtual void on_symbol_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; + virtual void on_f64_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) override; + /** update exprstate in response to a successfully-parsed subexpression **/ + virtual void on_expr(ref::brw expr, + exprstatestack * p_stack, + rp * p_emit_expr) override; + private: }; diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 61ba3319..9f20dfd8 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -87,12 +87,6 @@ namespace xo { virtual bool admits_semicolon() const; /** true iff this parsing state admits a singleassign '=' as next token **/ virtual bool admits_singleassign() const; -#ifdef OBSOLETE - /** true iff this parsing state admits a leftparen '(' as next token **/ - virtual bool admits_leftparen() const; - /** truee iff this parsing state admits a rightparen ')' as next token **/ - virtual bool admits_rightparen() const; -#endif /** update exprstate in response to incoming token @p tk, * forward instructions to parent parser diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index be19b8bc..131bfc42 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -38,6 +38,41 @@ namespace xo { p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); } + void + expect_expr_xs::on_symbol_token(const token_type & /*tk*/, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + /* todo: treat symbol as variable name */ + + /* various possibilities when looking for rhs expression: + * + * x := y // (1) + * x := f(a) // (2) + * x := f(a,b) // (3) + * + * need lookahead token following symbol to distinguish + * between (1) (symbol completes rhs expression) + * and {(2), (3)} (symbol is function call) + */ + + /* have to do pop first, before sending symbol to + * the o.g. symbol-requester + */ +#ifdef NOT_YET + p_stack->push_exprstate(exprstate(exprstatetype::expr_progress, + Variable::make(name, type))); +#endif + +#ifdef LATER + p_stack->pop_exprstate(); + p_stack->top_exprstate().on_symbol(tk.text(), + p_stack, p_emit_expr); +#endif + return; + } + + void expect_expr_xs::on_f64_token(const token_type & tk, exprstatestack * p_stack, @@ -57,6 +92,24 @@ namespace xo { (Constant::make(tk.f64_value()))); } + void + expect_expr_xs::on_expr(ref::brw expr, + exprstatestack * p_stack, + rp * p_emit_expr) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("exstype", this->exs_type_), + xtag("expr", expr)); + + + std::unique_ptr self = p_stack->pop_exprstate(); + + p_stack->top_exprstate().on_expr(expr, + p_stack, + p_emit_expr); + } /*on_expr*/ } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 31553c1c..540bcaaa 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -50,14 +50,11 @@ namespace xo { case exprstatetype::defexpr: case exprstatetype::parenexpr: + case exprstatetype::expect_rhs_expression: /* unreachable */ assert(false); return false; - case exprstatetype::expect_rhs_expression: - /* treat symbol as variable name */ - return true; - case exprstatetype::expect_symbol: return true; @@ -179,40 +176,6 @@ namespace xo { return false; } -#ifdef OBSOLETE - bool - exprstate::admits_rightparen() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - return false; - - case exprstatetype::defexpr: - case exprstatetype::parenexpr: - /* unreachable - redirects to define_xs */ - assert(false); - return false; - - case exprstatetype::expect_rhs_expression: - return false; - - case exprstatetype::expect_type: - return false; - - case exprstatetype::expect_symbol: - return false; - - case exprstatetype::expr_progress: - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - assert(false); - return false; - } - - return false; - } -#endif - void exprstate::on_def_token(const token_type & tk, exprstatestack * /*p_stack*/) @@ -240,39 +203,11 @@ namespace xo { case exprstatetype::expect_toplevel_expression_sequence: case exprstatetype::defexpr: case exprstatetype::parenexpr: - /* unreachable - redirects to define_xs */ + case exprstatetype::expect_rhs_expression: + /* unreachable - redirected to define_xs etc */ assert(false); return; - case exprstatetype::expect_rhs_expression: - { - /* various possibilities when looking for rhs expression: - * - * x := y // (1) - * x := f(a) // (2) - * x := f(a,b) // (3) - * - * need lookahead token following symbol to distinguish - * between (1) (symbol completes rhs expression) - * and {(2), (3)} (symbol is function call) - */ - - /* have to do pop first, before sending symbol to - * the o.g. symbol-requester - */ -#ifdef NOT_YET - p_stack->push_exprstate(exprstate(exprstatetype::expr_progress, - Variable::make(name, type))); -#endif - -#ifdef LATER - p_stack->pop_exprstate(); - p_stack->top_exprstate().on_symbol(tk.text(), - p_stack, p_emit_expr); -#endif - return; - } - case exprstatetype::expect_symbol: { /* have to do pop first, before sending symbol to @@ -528,8 +463,8 @@ namespace xo { void exprstate::on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * p_emit_expr) + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -537,42 +472,7 @@ namespace xo { log && log(xtag("exstype", this->exs_type_), xtag("expr", expr)); - switch (this->exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::defexpr: - case exprstatetype::parenexpr: - /* unreachable. redirects to define_xs::on_expr() etc */ - assert(false); - return; - - case exprstatetype::expect_rhs_expression: { - - std::unique_ptr self = p_stack->pop_exprstate(); - - p_stack->top_exprstate().on_expr(expr, - p_stack, - p_emit_expr); - - return; - } - - case exprstatetype::expect_type: - case exprstatetype::expect_symbol: - /* unreachable - * (this exprstate issues pop instruction from exprstate::on_input() - */ - assert(false); - return; - case exprstatetype::expr_progress: - /* unreachable */ - assert(false); - return; - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - assert(false); - return; - } + assert(false); } /*on_expr*/ void From be412cb5aef734f5ee2e4ae99f9b5fbaddd5e92b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 Aug 2024 16:20:51 -0500 Subject: [PATCH 1369/2524] xo-reader: refactor: + expect_symbol_xs [wip] --- include/xo/reader/expect_symbol_xs.hpp | 27 ++++++++++++++++++++++++++ include/xo/reader/exprstate.hpp | 2 ++ src/reader/CMakeLists.txt | 3 ++- src/reader/expect_symbol_xs.cpp | 23 ++++++++++++++++++++++ src/reader/exprseq_xs.cpp | 3 ++- src/reader/exprstate.cpp | 6 ------ 6 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 include/xo/reader/expect_symbol_xs.hpp create mode 100644 src/reader/expect_symbol_xs.cpp diff --git a/include/xo/reader/expect_symbol_xs.hpp b/include/xo/reader/expect_symbol_xs.hpp new file mode 100644 index 00000000..103b89aa --- /dev/null +++ b/include/xo/reader/expect_symbol_xs.hpp @@ -0,0 +1,27 @@ +/* file expect_symbol_xs.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "exprstate.hpp" + +namespace xo { + namespace scm { + /** @class expect_symbol_xs + * @brief state machine to expect + capture a symbol + * + * For example, lhs in a define-expression + **/ + class expect_symbol_xs : public exprstate { + public: + expect_symbol_xs(); + + static std::unique_ptr expect_symbol_expression(); + }; + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end expect_symbol_xs.hpp */ diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 9f20dfd8..7f643921 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -70,9 +70,11 @@ namespace xo { {} virtual ~exprstate() = default; +#ifdef RELOCATED static std::unique_ptr expect_symbol() { return std::make_unique(exprstate(exprstatetype::expect_symbol)); } +#endif static std::unique_ptr expect_type() { return std::make_unique(exprstate(exprstatetype::expect_type)); } diff --git a/src/reader/CMakeLists.txt b/src/reader/CMakeLists.txt index 77a06a63..473c9a93 100644 --- a/src/reader/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -9,7 +9,8 @@ set(SELF_SRCS progress_xs.cpp paren_xs.cpp exprseq_xs.cpp - expect_expr_xs.cpp) + expect_expr_xs.cpp + expect_symbol_xs.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_dependency(${SELF_LIB} xo_expression) diff --git a/src/reader/expect_symbol_xs.cpp b/src/reader/expect_symbol_xs.cpp new file mode 100644 index 00000000..afc8bf6e --- /dev/null +++ b/src/reader/expect_symbol_xs.cpp @@ -0,0 +1,23 @@ +/* file expect_symbol_xs.cpp + * + * author: Roland Conybeare + */ + +#include "expect_symbol_xs.hpp" + +namespace xo { + namespace scm { + std::unique_ptr + expect_symbol_xs::expect_symbol_expression() { + return std::make_unique(expect_symbol_xs()); + } + + expect_symbol_xs::expect_symbol_xs() + : exprstate(exprstatetype::expect_symbol) + {} + + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end expect_symbol_xs.cpp */ diff --git a/src/reader/exprseq_xs.cpp b/src/reader/exprseq_xs.cpp index 502f129f..042c5eb3 100644 --- a/src/reader/exprseq_xs.cpp +++ b/src/reader/exprseq_xs.cpp @@ -2,6 +2,7 @@ #include "exprseq_xs.hpp" #include "define_xs.hpp" +#include "expect_symbol_xs.hpp" namespace xo { namespace scm { @@ -30,7 +31,7 @@ namespace xo { /* todo: replace: * expect_symbol_or_function_signature() */ - p_stack->push_exprstate(exprstate::expect_symbol()); + p_stack->push_exprstate(expect_symbol_xs::expect_symbol_expression()); /* keyword 'def' introduces a definition: * def pi : f64 = 3.14159265 diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 540bcaaa..9c083656 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -87,9 +87,6 @@ namespace xo { return false; case exprstatetype::expect_rhs_expression: - /* rhs-expressions (or expressions for that matter) - * may not begin with a colon - */ case exprstatetype::expect_symbol: case exprstatetype::expect_type: return false; @@ -155,9 +152,6 @@ namespace xo { return false; case exprstatetype::expect_rhs_expression: - /* rhs-expressions (or expressions for that matter) - * may not begin with singleassign '=' - */ case exprstatetype::expect_symbol: case exprstatetype::expect_type: return false; From 08497a371c3c6aba070eca40fc0314456b52fa71 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 Aug 2024 16:56:35 -0500 Subject: [PATCH 1370/2524] xo-reader: mv symbol handling to expect_symbol_xs --- include/xo/reader/expect_symbol_xs.hpp | 4 ++++ src/reader/expect_symbol_xs.cpp | 14 ++++++++++++++ src/reader/exprstate.cpp | 13 +------------ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/include/xo/reader/expect_symbol_xs.hpp b/include/xo/reader/expect_symbol_xs.hpp index 103b89aa..144bc3d0 100644 --- a/include/xo/reader/expect_symbol_xs.hpp +++ b/include/xo/reader/expect_symbol_xs.hpp @@ -19,6 +19,10 @@ namespace xo { expect_symbol_xs(); static std::unique_ptr expect_symbol_expression(); + + virtual void on_symbol_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/expect_symbol_xs.cpp b/src/reader/expect_symbol_xs.cpp index afc8bf6e..84faeaa3 100644 --- a/src/reader/expect_symbol_xs.cpp +++ b/src/reader/expect_symbol_xs.cpp @@ -16,6 +16,20 @@ namespace xo { : exprstate(exprstatetype::expect_symbol) {} + void + expect_symbol_xs::on_symbol_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) + { + /* have to do pop first, before sending symbol to + * the o.g. symbol-requester + */ + std::unique_ptr self = p_stack->pop_exprstate(); + + p_stack->top_exprstate().on_symbol(tk.text(), + p_stack, p_emit_expr); + return; + } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 9c083656..aa916b0d 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -198,22 +198,11 @@ namespace xo { case exprstatetype::defexpr: case exprstatetype::parenexpr: case exprstatetype::expect_rhs_expression: + case exprstatetype::expect_symbol: /* unreachable - redirected to define_xs etc */ assert(false); return; - case exprstatetype::expect_symbol: - { - /* have to do pop first, before sending symbol to - * the o.g. symbol-requester - */ - std::unique_ptr self = p_stack->pop_exprstate(); - - p_stack->top_exprstate().on_symbol(tk.text(), - p_stack, p_emit_expr); - return; - } - case exprstatetype::expect_type: { TypeDescr td = nullptr; From 1087e57a624d984855e6e242531f6a78a764956e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 Aug 2024 17:02:50 -0500 Subject: [PATCH 1371/2524] xo-reader: refactor: + expect_type_xs [wip] --- include/xo/reader/expect_type_xs.hpp | 25 +++++++++++++++++++++++++ include/xo/reader/exprstate.hpp | 2 ++ src/reader/CMakeLists.txt | 3 ++- src/reader/define_xs.cpp | 3 ++- src/reader/expect_type_xs.cpp | 24 ++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 include/xo/reader/expect_type_xs.hpp create mode 100644 src/reader/expect_type_xs.cpp diff --git a/include/xo/reader/expect_type_xs.hpp b/include/xo/reader/expect_type_xs.hpp new file mode 100644 index 00000000..261074f9 --- /dev/null +++ b/include/xo/reader/expect_type_xs.hpp @@ -0,0 +1,25 @@ +/* file expect_type_xs.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "exprstate.hpp" + +namespace xo { + namespace scm { + /** @class expect_type_xs + * @brief state-machine for accepting a typename-expression + **/ + class expect_type_xs : public exprstate { + public: + expect_type_xs(); + + static std::unique_ptr make(); + }; + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end expect_type_xs.hpp */ diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 7f643921..b3cdf24e 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -75,9 +75,11 @@ namespace xo { return std::make_unique(exprstate(exprstatetype::expect_symbol)); } #endif +#ifdef RELOCATED static std::unique_ptr expect_type() { return std::make_unique(exprstate(exprstatetype::expect_type)); } +#endif exprstatetype exs_type() const { return exs_type_; } diff --git a/src/reader/CMakeLists.txt b/src/reader/CMakeLists.txt index 473c9a93..6907dff2 100644 --- a/src/reader/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -10,7 +10,8 @@ set(SELF_SRCS paren_xs.cpp exprseq_xs.cpp expect_expr_xs.cpp - expect_symbol_xs.cpp) + expect_symbol_xs.cpp + expect_type_xs.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_dependency(${SELF_LIB} xo_expression) diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index ffa8a094..ea8a72a1 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -2,6 +2,7 @@ #include "define_xs.hpp" #include "expect_expr_xs.hpp" +#include "expect_type_xs.hpp" namespace xo { namespace scm { @@ -321,7 +322,7 @@ namespace xo { if (this->defxs_type_ == defexprstatetype::def_1) { this->defxs_type_ = defexprstatetype::def_2; - p_stack->push_exprstate(exprstate::expect_type()); + p_stack->push_exprstate(expect_type_xs::make()); } else { assert(false); } diff --git a/src/reader/expect_type_xs.cpp b/src/reader/expect_type_xs.cpp new file mode 100644 index 00000000..65f0f7a9 --- /dev/null +++ b/src/reader/expect_type_xs.cpp @@ -0,0 +1,24 @@ +/* file expect_type_xs.cpp + * + * author: Roland Conybeare + */ + +#include "expect_type_xs.hpp" +#include "exprstate.hpp" + +namespace xo { + namespace scm { + std::unique_ptr + expect_type_xs::make() { + return std::make_unique(expect_type_xs()); + } + + expect_type_xs::expect_type_xs() + : exprstate(exprstatetype::expect_type) + {} + + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end expect_type_xs.cpp */ From 29f21730701695bec32b8c5c7b7f2c0948ceba12 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 Aug 2024 17:08:40 -0500 Subject: [PATCH 1372/2524] xo-reader: refactor: mv type-expr handling to expect_type_xs --- include/xo/reader/expect_type_xs.hpp | 4 ++++ src/reader/expect_type_xs.cpp | 36 ++++++++++++++++++++++++++++ src/reader/exprstate.cpp | 32 ++----------------------- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/include/xo/reader/expect_type_xs.hpp b/include/xo/reader/expect_type_xs.hpp index 261074f9..c75c91cb 100644 --- a/include/xo/reader/expect_type_xs.hpp +++ b/include/xo/reader/expect_type_xs.hpp @@ -17,6 +17,10 @@ namespace xo { expect_type_xs(); static std::unique_ptr make(); + + virtual void on_symbol_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/expect_type_xs.cpp b/src/reader/expect_type_xs.cpp index 65f0f7a9..6c4c5afb 100644 --- a/src/reader/expect_type_xs.cpp +++ b/src/reader/expect_type_xs.cpp @@ -5,8 +5,11 @@ #include "expect_type_xs.hpp" #include "exprstate.hpp" +#include "xo/reflect/Reflect.hpp" namespace xo { + using xo::reflect::Reflect; + namespace scm { std::unique_ptr expect_type_xs::make() { @@ -17,6 +20,39 @@ namespace xo { : exprstate(exprstatetype::expect_type) {} + void + expect_type_xs::on_symbol_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) + { + const char * c_self_name = "expect_type_xs::on_symbol_token"; + + TypeDescr td = nullptr; + + /* TODO: replace with typetable lookup */ + + if (tk.text() == "f64") + td = Reflect::require(); + else if(tk.text() == "f32") + td = Reflect::require(); + else if(tk.text() == "i16") + td = Reflect::require(); + else if(tk.text() == "i32") + td = Reflect::require(); + else if(tk.text() == "i64") + td = Reflect::require(); + + if (!td) { + throw std::runtime_error + (tostr(c_self_name, + ": unknown type name", + " (expecting f64|f32|i16|i32|i64)", + xtag("typename", tk.text()))); + } + + std::unique_ptr self = p_stack->pop_exprstate(); + p_stack->top_exprstate().on_typedescr(td, p_stack, p_emit_expr); + } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index aa916b0d..1b7bd878 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -180,7 +180,7 @@ namespace xo { void exprstate::on_symbol_token(const token_type & tk, exprstatestack * p_stack, - rp * p_emit_expr) + rp * /*p_emit_expr*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -199,39 +199,11 @@ namespace xo { case exprstatetype::parenexpr: case exprstatetype::expect_rhs_expression: case exprstatetype::expect_symbol: + case exprstatetype::expect_type: /* unreachable - redirected to define_xs etc */ assert(false); return; - case exprstatetype::expect_type: { - TypeDescr td = nullptr; - - /* TODO: replace with typetable lookup */ - - if (tk.text() == "f64") - td = Reflect::require(); - else if(tk.text() == "f32") - td = Reflect::require(); - else if(tk.text() == "i16") - td = Reflect::require(); - else if(tk.text() == "i32") - td = Reflect::require(); - else if(tk.text() == "i64") - td = Reflect::require(); - - if (!td) { - throw std::runtime_error - (tostr(c_self_name, - ": unknown type name", - " (expecting f64|f32|i16|i32|i64)", - xtag("typename", tk.text()))); - } - - std::unique_ptr self = p_stack->pop_exprstate(); - p_stack->top_exprstate().on_typedescr(td, p_stack, p_emit_expr); - return; - } - case exprstatetype::expr_progress: /* unreachable */ assert(false); From 99d9f400912f991ffcdbe8d65ca0d3a090d595c5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 Aug 2024 17:09:51 -0500 Subject: [PATCH 1373/2524] xo-reader: streamline: collapse exprstate::on_symbol_token() --- src/reader/exprstate.cpp | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 1b7bd878..051a023d 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -189,32 +189,7 @@ namespace xo { constexpr const char * c_self_name = "exprstate::on_symbol_token"; - if (!this->admits_symbol()) { - this->illegal_input_error(c_self_name, tk); - } - - switch (this->exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::defexpr: - case exprstatetype::parenexpr: - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - /* unreachable - redirected to define_xs etc */ - assert(false); - return; - - case exprstatetype::expr_progress: - /* unreachable */ - assert(false); - return; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - assert(false); - return; - } + this->illegal_input_error(c_self_name, tk); } void From 2b436850cf3e12f4cab6d7dd04f62dae568c1dcf Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 Aug 2024 17:12:52 -0500 Subject: [PATCH 1374/2524] xo-reader: simplify: drop exprstate.admits_colon() --- include/xo/reader/define_xs.hpp | 2 +- include/xo/reader/exprstate.hpp | 2 -- include/xo/reader/paren_xs.hpp | 4 ---- include/xo/reader/progress_xs.hpp | 1 - src/reader/exprstate.cpp | 38 +++---------------------------- src/reader/paren_xs.cpp | 3 --- src/reader/progress_xs.cpp | 3 --- 7 files changed, 4 insertions(+), 49 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 8c9ef9c1..dc5a5944 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -43,9 +43,9 @@ namespace xo { defexprstatetype defxs_type() const { return defxs_type_; } bool admits_rightparen() const; + bool admits_colon() const; virtual bool admits_symbol() const override; - virtual bool admits_colon() const override; virtual bool admits_semicolon() const override; virtual bool admits_singleassign() const override; diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index b3cdf24e..4da51d5d 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -85,8 +85,6 @@ namespace xo { /** true iff this parsing state admits a symbol as next token **/ virtual bool admits_symbol() const; - /** true iff this parsing state admits a colon as next token **/ - virtual bool admits_colon() const; /** true iff this parsing state admits a semicolon as next token **/ virtual bool admits_semicolon() const; /** true iff this parsing state admits a singleassign '=' as next token **/ diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index d00dc384..61a057ae 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -35,12 +35,8 @@ namespace xo { bool admits_rightparen() const; virtual bool admits_symbol() const override; - virtual bool admits_colon() const override; virtual bool admits_semicolon() const override; virtual bool admits_singleassign() const override; -#ifdef OBSOLETE - virtual bool admits_leftparen() const override; -#endif virtual void on_expr(ref::brw expr, exprstatestack * p_stack, diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index d4a0c190..47912d20 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -25,7 +25,6 @@ namespace xo { bool admits_f64() const; virtual bool admits_symbol() const override; - virtual bool admits_colon() const override; virtual bool admits_semicolon() const override; virtual bool admits_singleassign() const override; #ifdef OBSOLETE diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 051a023d..2eaac5bb 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -76,35 +76,6 @@ namespace xo { return false; } - bool - exprstate::admits_colon() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::defexpr: - case exprstatetype::parenexpr: - /* unreachable -- redirects to define_xs::admits_colon() etc */ - assert(false); - return false; - - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - return false; - - case exprstatetype::expr_progress: - /* unreachable */ - assert(false); - return false; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - return false; - } - - return false; - } - bool exprstate::admits_semicolon() const { switch (exs_type_) { @@ -214,12 +185,9 @@ namespace xo { constexpr const char * self_name = "exprstate::on_colon"; /* lots of illegal states */ - if (!this->admits_colon()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected colon for parsing state", - xtag("state", *this))); - } + throw std::runtime_error(tostr(self_name, + ": unexpected colon for parsing state", + xtag("state", *this))); assert(false); } diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 2fcf427b..1f882b9f 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -17,9 +17,6 @@ namespace xo { bool paren_xs::admits_symbol() const { return true; } - bool - paren_xs::admits_colon() const { return false; } - bool paren_xs::admits_semicolon() const { return false; } diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index f518374a..16f31ce2 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -17,9 +17,6 @@ namespace xo { bool progress_xs::admits_symbol() const { return false; } - bool - progress_xs::admits_colon() const { return false; } - bool progress_xs::admits_semicolon() const { return true; } From 57b9a7f561c1034c7d4f3a1c5b1a6346d14a5ba3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 Aug 2024 17:14:19 -0500 Subject: [PATCH 1375/2524] xo-reader: tidy: cleanup debris --- include/xo/reader/exprstate.hpp | 11 ----------- src/reader/paren_xs.cpp | 5 ----- 2 files changed, 16 deletions(-) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 4da51d5d..b6807776 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -70,17 +70,6 @@ namespace xo { {} virtual ~exprstate() = default; -#ifdef RELOCATED - static std::unique_ptr expect_symbol() { - return std::make_unique(exprstate(exprstatetype::expect_symbol)); - } -#endif -#ifdef RELOCATED - static std::unique_ptr expect_type() { - return std::make_unique(exprstate(exprstatetype::expect_type)); - } -#endif - exprstatetype exs_type() const { return exs_type_; } /** true iff this parsing state admits a symbol as next token **/ diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 1f882b9f..232bdb72 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -23,11 +23,6 @@ namespace xo { bool paren_xs::admits_singleassign() const { return false; } -#ifdef OBSOLETE - bool - paren_xs::admits_leftparen() const { /*unreachable*/ return false; } -#endif - bool paren_xs::admits_rightparen() const { switch (parenxs_type_) { From 18a4b51d3cd7a64550b71166c71be429b1a9b973 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 Aug 2024 17:30:45 -0500 Subject: [PATCH 1376/2524] xo-reader: simplify: drop redundant virtual admits_xxx() --- include/xo/reader/define_xs.hpp | 2 +- include/xo/reader/exprstate.hpp | 2 ++ include/xo/reader/paren_xs.hpp | 1 - include/xo/reader/progress_xs.hpp | 5 ----- src/reader/exprstate.cpp | 13 +++++-------- src/reader/paren_xs.cpp | 3 --- src/reader/progress_xs.cpp | 10 ++-------- 7 files changed, 10 insertions(+), 26 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index dc5a5944..5e70012c 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -44,9 +44,9 @@ namespace xo { bool admits_rightparen() const; bool admits_colon() const; + bool admits_semicolon() const; virtual bool admits_symbol() const override; - virtual bool admits_semicolon() const override; virtual bool admits_singleassign() const override; // virtual void on_f64(..) override diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index b6807776..c75870ce 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -74,8 +74,10 @@ namespace xo { /** true iff this parsing state admits a symbol as next token **/ virtual bool admits_symbol() const; +#ifdef OBSOLETE /** true iff this parsing state admits a semicolon as next token **/ virtual bool admits_semicolon() const; +#endif /** true iff this parsing state admits a singleassign '=' as next token **/ virtual bool admits_singleassign() const; diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 61a057ae..058cdf37 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -35,7 +35,6 @@ namespace xo { bool admits_rightparen() const; virtual bool admits_symbol() const override; - virtual bool admits_semicolon() const override; virtual bool admits_singleassign() const override; virtual void on_expr(ref::brw expr, diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 47912d20..9ec61099 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -25,12 +25,7 @@ namespace xo { bool admits_f64() const; virtual bool admits_symbol() const override; - virtual bool admits_semicolon() const override; virtual bool admits_singleassign() const override; -#ifdef OBSOLETE - virtual bool admits_leftparen() const override; - virtual bool admits_rightparen() const override; -#endif virtual void on_expr(ref::brw expr, exprstatestack * p_stack, diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 2eaac5bb..3416f7d1 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -76,6 +76,7 @@ namespace xo { return false; } +#ifdef OBSOLETE bool exprstate::admits_semicolon() const { switch (exs_type_) { @@ -97,6 +98,7 @@ namespace xo { return false; } +#endif bool exprstate::admits_singleassign() const { @@ -202,14 +204,9 @@ namespace xo { constexpr const char * self_name = "exprstate::on_semicolon"; - if (!this->admits_semicolon()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected semicolon for parsing state", - xtag("state", *this))); - } - - assert(false); + throw std::runtime_error(tostr(self_name, + ": unexpected semicolon for parsing state", + xtag("state", *this))); } void diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 232bdb72..448e0faa 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -17,9 +17,6 @@ namespace xo { bool paren_xs::admits_symbol() const { return true; } - bool - paren_xs::admits_semicolon() const { return false; } - bool paren_xs::admits_singleassign() const { return false; } diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 16f31ce2..3a978505 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -17,20 +17,14 @@ namespace xo { bool progress_xs::admits_symbol() const { return false; } +#ifdef OSBOLETE bool progress_xs::admits_semicolon() const { return true; } +#endif bool progress_xs::admits_singleassign() const { return false; } -#ifdef OBSOLETE - bool - progress_xs::admits_rightparen() const { - /* satisfies expression form */ - return true; - } -#endif - bool progress_xs::admits_f64() const { return false; } From f1f5b44a8182d6b24d955fa36dc264f718a8c009 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 Aug 2024 17:31:44 -0500 Subject: [PATCH 1377/2524] xo-reader: tidy: buty debris --- src/reader/exprstate.cpp | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 3416f7d1..cb85ae6a 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -76,30 +76,6 @@ namespace xo { return false; } -#ifdef OBSOLETE - bool - exprstate::admits_semicolon() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - case exprstatetype::defexpr: - case exprstatetype::parenexpr: - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - return false; - case exprstatetype::expr_progress: - /* unreachable */ - assert(false); - return true; - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - return false; - } - - return false; - } -#endif - bool exprstate::admits_singleassign() const { switch (exs_type_) { From e9e0d5255fbd88fc066e9689a7bdd7805dd7cd02 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 Aug 2024 23:57:01 -0500 Subject: [PATCH 1378/2524] xo-reader: streamline: drop redundant admits_xxx() methods --- include/xo/reader/define_xs.hpp | 4 +++- include/xo/reader/exprstate.hpp | 4 +--- include/xo/reader/paren_xs.hpp | 1 - include/xo/reader/progress_xs.hpp | 4 +++- src/reader/define_xs.cpp | 36 +++++++++++++++---------------- src/reader/exprstate.cpp | 13 +++++------ src/reader/paren_xs.cpp | 3 --- src/reader/progress_xs.cpp | 8 ------- 8 files changed, 29 insertions(+), 44 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 5e70012c..d00b2d79 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -47,7 +47,9 @@ namespace xo { bool admits_semicolon() const; virtual bool admits_symbol() const override; - virtual bool admits_singleassign() const override; +#ifdef OBSOLETE + bool admits_singleassign() const; +#endif // virtual void on_f64(..) override virtual void on_expr(ref::brw expr, diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index c75870ce..2577c920 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -75,11 +75,9 @@ namespace xo { /** true iff this parsing state admits a symbol as next token **/ virtual bool admits_symbol() const; #ifdef OBSOLETE - /** true iff this parsing state admits a semicolon as next token **/ - virtual bool admits_semicolon() const; -#endif /** true iff this parsing state admits a singleassign '=' as next token **/ virtual bool admits_singleassign() const; +#endif /** update exprstate in response to incoming token @p tk, * forward instructions to parent parser diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 058cdf37..3f4922de 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -35,7 +35,6 @@ namespace xo { bool admits_rightparen() const; virtual bool admits_symbol() const override; - virtual bool admits_singleassign() const override; virtual void on_expr(ref::brw expr, exprstatestack * p_stack, diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 9ec61099..93e442e3 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -25,7 +25,9 @@ namespace xo { bool admits_f64() const; virtual bool admits_symbol() const override; - virtual bool admits_singleassign() const override; +#ifdef OBSOLETE + bool admits_singleassign() const; +#endif virtual void on_expr(ref::brw expr, exprstatestack * p_stack, diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index ea8a72a1..d901f3fb 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -88,24 +88,11 @@ namespace xo { return false; } +#ifdef OBSOLETE bool define_xs::admits_singleassign() const { switch (defxs_type_) { - /* - * def foo = 1 ; - * def foo : f64 = 1 ; - * ^ ^ ^ ^ ^ ^ ^ - * | | | | | | (done) - * | | | | | def_4:expect_rhs_expression - * | | | | def_3 - * | | | def_2:expect_type - * | | def_1 - * | def_0:expect_symbol - * expect_toplevel_expression_sequence - * - * note that we skip from def_1 -> def_4 if '=' instead of ':' - */ case defexprstatetype::def_0: return false; @@ -131,6 +118,7 @@ namespace xo { return false; } +#endif #ifdef OBSOLETE bool @@ -367,10 +355,20 @@ namespace xo { constexpr const char * self_name = "exprstate::on_singleassign"; - if (!this->admits_singleassign()) { - this->illegal_input_error(self_name, tk); - } - + /* + * def foo = 1 ; + * def foo : f64 = 1 ; + * ^ ^ ^ ^ ^ ^ ^ + * | | | | | | (done) + * | | | | | def_4:expect_rhs_expression + * | | | | def_3 + * | | | def_2:expect_type + * | | def_1 + * | def_0:expect_symbol + * expect_toplevel_expression_sequence + * + * note that we skip from def_1 -> def_4 if '=' instead of ':' + */ if ((this->defxs_type_ == defexprstatetype::def_1) || (this->defxs_type_ == defexprstatetype::def_3)) { @@ -378,7 +376,7 @@ namespace xo { p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); } else { - assert(false); + this->illegal_input_error(self_name, tk); } } diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index cb85ae6a..869056b1 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -76,6 +76,7 @@ namespace xo { return false; } +#ifdef OBSOLETE bool exprstate::admits_singleassign() const { switch (exs_type_) { @@ -118,6 +119,7 @@ namespace xo { return false; } +#endif void exprstate::on_def_token(const token_type & tk, @@ -193,14 +195,9 @@ namespace xo { constexpr const char * self_name = "exprstate::on_singleassign_token"; - if (!this->admits_singleassign()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected equals for parsing state", - xtag("state", *this))); - } - - assert(false); + throw std::runtime_error(tostr(self_name, + ": unexpected equals for parsing state", + xtag("state", *this))); } void diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 448e0faa..304babb6 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -17,9 +17,6 @@ namespace xo { bool paren_xs::admits_symbol() const { return true; } - bool - paren_xs::admits_singleassign() const { return false; } - bool paren_xs::admits_rightparen() const { switch (parenxs_type_) { diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 3a978505..077450eb 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -17,14 +17,6 @@ namespace xo { bool progress_xs::admits_symbol() const { return false; } -#ifdef OSBOLETE - bool - progress_xs::admits_semicolon() const { return true; } -#endif - - bool - progress_xs::admits_singleassign() const { return false; } - bool progress_xs::admits_f64() const { return false; } From 652a454887082164343aa1de2de0b1fb002e120e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 Aug 2024 23:58:08 -0500 Subject: [PATCH 1379/2524] xo-reader: bury debris --- include/xo/reader/exprstate.hpp | 4 --- src/reader/exprstate.cpp | 45 --------------------------------- 2 files changed, 49 deletions(-) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 2577c920..9f6e46d2 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -74,10 +74,6 @@ namespace xo { /** true iff this parsing state admits a symbol as next token **/ virtual bool admits_symbol() const; -#ifdef OBSOLETE - /** true iff this parsing state admits a singleassign '=' as next token **/ - virtual bool admits_singleassign() const; -#endif /** update exprstate in response to incoming token @p tk, * forward instructions to parent parser diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 869056b1..ce33737d 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -76,51 +76,6 @@ namespace xo { return false; } -#ifdef OBSOLETE - bool - exprstate::admits_singleassign() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - /* - * def foo = 1 ; - * def foo : f64 = 1 ; - * ^ ^ ^ ^ ^ ^ ^ - * | | | | | | (done) - * | | | | | def_4:expect_rhs_expression - * | | | | def_3 - * | | | def_2:expect_type - * | | def_1 - * | def_0:expect_symbol - * expect_toplevel_expression_sequence - * - * note that we skip from def_1 -> def_4 if '=' instead of ':' - */ - case exprstatetype::defexpr: - case exprstatetype::parenexpr: - /* unreachable - redirects to define_xs etrc */ - assert(false); - return false; - - case exprstatetype::expect_rhs_expression: - case exprstatetype::expect_symbol: - case exprstatetype::expect_type: - return false; - - case exprstatetype::expr_progress: - /* unreachable */ - assert(false); - return false; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - return false; - } - - return false; - } -#endif - void exprstate::on_def_token(const token_type & tk, exprstatestack * /*p_stack*/) From ae746b31736209674d265b8fb565f02ff3591ed2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 11 Aug 2024 00:01:11 -0500 Subject: [PATCH 1380/2524] xo-reader: retire unused admits_symbol() --- include/xo/reader/define_xs.hpp | 5 +---- include/xo/reader/exprstate.hpp | 6 +++++- include/xo/reader/paren_xs.hpp | 2 -- include/xo/reader/progress_xs.hpp | 5 ----- src/reader/define_xs.cpp | 22 ---------------------- src/reader/exprstate.cpp | 2 ++ src/reader/paren_xs.cpp | 3 --- src/reader/progress_xs.cpp | 3 --- 8 files changed, 8 insertions(+), 40 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index d00b2d79..f3a5f15a 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -46,10 +46,7 @@ namespace xo { bool admits_colon() const; bool admits_semicolon() const; - virtual bool admits_symbol() const override; -#ifdef OBSOLETE - bool admits_singleassign() const; -#endif + //bool admits_symbol() const; // virtual void on_f64(..) override virtual void on_expr(ref::brw expr, diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 9f6e46d2..8cb278e7 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -72,13 +72,17 @@ namespace xo { exprstatetype exs_type() const { return exs_type_; } +#ifdef OBSOLETE /** true iff this parsing state admits a symbol as next token **/ virtual bool admits_symbol() const; +#endif /** update exprstate in response to incoming token @p tk, * forward instructions to parent parser **/ - void on_input(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); + void on_input(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); /** update exprstate in response to a successfully-parsed subexpression **/ virtual void on_expr(ref::brw expr, diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 3f4922de..65811607 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -34,8 +34,6 @@ namespace xo { bool admits_f64() const; bool admits_rightparen() const; - virtual bool admits_symbol() const override; - virtual void on_expr(ref::brw expr, exprstatestack * p_stack, rp * p_emit_expr) override; diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 93e442e3..361e6d3d 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -24,11 +24,6 @@ namespace xo { bool admits_f64() const; - virtual bool admits_symbol() const override; -#ifdef OBSOLETE - bool admits_singleassign() const; -#endif - virtual void on_expr(ref::brw expr, exprstatestack * p_stack, rp * p_emit_expr) override; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index d901f3fb..d3c2d5c2 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -17,28 +17,6 @@ namespace xo { def_expr_{std::move(def_expr)} {} - bool - define_xs::admits_symbol() const { - switch (defxs_type_) { - - case defexprstatetype::def_0: - case defexprstatetype::def_1: - case defexprstatetype::def_2: - case defexprstatetype::def_3: - case defexprstatetype::def_4: - case defexprstatetype::def_5: - return false; - - case defexprstatetype::invalid: - case defexprstatetype::n_defexprstatetype: - /* unreachable */ - assert(false); - return false; - } - - return false; - } - bool define_xs::admits_colon() const { switch (defxs_type_) { diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index ce33737d..c3b85833 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -42,6 +42,7 @@ namespace xo { return "???"; } +#ifdef OBSOLETE bool exprstate::admits_symbol() const { switch (exs_type_) { @@ -75,6 +76,7 @@ namespace xo { return false; } +#endif void exprstate::on_def_token(const token_type & tk, diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 304babb6..cfd20f95 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -14,9 +14,6 @@ namespace xo { return std::make_unique(paren_xs()); } - bool - paren_xs::admits_symbol() const { return true; } - bool paren_xs::admits_rightparen() const { switch (parenxs_type_) { diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 077450eb..4d78fb8a 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -14,9 +14,6 @@ namespace xo { gen_expr_{std::move(valex)} {} - bool - progress_xs::admits_symbol() const { return false; } - bool progress_xs::admits_f64() const { return false; } From 4f44bada3dd971927396a3a4568827a812c9c42a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 11 Aug 2024 00:02:29 -0500 Subject: [PATCH 1381/2524] xo-reader: tidy - bury debris --- include/xo/reader/exprstate.hpp | 5 ----- src/reader/exprstate.cpp | 38 --------------------------------- 2 files changed, 43 deletions(-) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 8cb278e7..dc62e86f 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -72,11 +72,6 @@ namespace xo { exprstatetype exs_type() const { return exs_type_; } -#ifdef OBSOLETE - /** true iff this parsing state admits a symbol as next token **/ - virtual bool admits_symbol() const; -#endif - /** update exprstate in response to incoming token @p tk, * forward instructions to parent parser **/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index c3b85833..f38f3540 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -5,9 +5,7 @@ #include "progress_xs.hpp" #include "paren_xs.hpp" #include "expect_expr_xs.hpp" -//#include "xo/expression/DefineExpr.hpp" #include "xo/expression/Constant.hpp" -//#include "xo/expression/ConvertExpr.hpp" #include "xo/reflect/Reflect.hpp" namespace xo { @@ -42,42 +40,6 @@ namespace xo { return "???"; } -#ifdef OBSOLETE - bool - exprstate::admits_symbol() const { - switch (exs_type_) { - case exprstatetype::expect_toplevel_expression_sequence: - return false; - - case exprstatetype::defexpr: - case exprstatetype::parenexpr: - case exprstatetype::expect_rhs_expression: - /* unreachable */ - assert(false); - return false; - - case exprstatetype::expect_symbol: - return true; - - case exprstatetype::expect_type: - /* treat symbol as typename */ - return true; - - case exprstatetype::expr_progress: - /* unreachable */ - assert(false); - return false; - - case exprstatetype::invalid: - case exprstatetype::n_exprstatetype: - /* unreachable */ - return false; - } - - return false; - } -#endif - void exprstate::on_def_token(const token_type & tk, exprstatestack * /*p_stack*/) From 1918148999755b71b8472a48febe2bffe71887ba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 11 Aug 2024 00:05:44 -0500 Subject: [PATCH 1382/2524] xo-reader: refactor: consolidate w/ illegal_input_error() --- src/reader/exprstate.cpp | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index f38f3540..e7933d7c 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -1,16 +1,16 @@ /* @file exprstate.cpp */ #include "exprstate.hpp" -#include "define_xs.hpp" -#include "progress_xs.hpp" -#include "paren_xs.hpp" -#include "expect_expr_xs.hpp" -#include "xo/expression/Constant.hpp" -#include "xo/reflect/Reflect.hpp" +//#include "define_xs.hpp" +//#include "progress_xs.hpp" +//#include "paren_xs.hpp" +//#include "expect_expr_xs.hpp" +//#include "xo/expression/Constant.hpp" +//#include "xo/reflect/Reflect.hpp" namespace xo { - using xo::ast::Constant; - using xo::reflect::Reflect; + //using xo::ast::Constant; + //using xo::reflect::Reflect; using xo::reflect::TypeDescr; namespace scm { @@ -75,7 +75,7 @@ namespace xo { } void - exprstate::on_colon_token(const token_type & /*tk*/, + exprstate::on_colon_token(const token_type & tk, exprstatestack * /*p_stack*/) { constexpr bool c_debug_flag = true; @@ -83,16 +83,11 @@ namespace xo { constexpr const char * self_name = "exprstate::on_colon"; - /* lots of illegal states */ - throw std::runtime_error(tostr(self_name, - ": unexpected colon for parsing state", - xtag("state", *this))); - - assert(false); + this->illegal_input_error(self_name, tk); } void - exprstate::on_semicolon_token(const token_type & /*tk*/, + exprstate::on_semicolon_token(const token_type & tk, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { @@ -101,22 +96,18 @@ namespace xo { constexpr const char * self_name = "exprstate::on_semicolon"; - throw std::runtime_error(tostr(self_name, - ": unexpected semicolon for parsing state", - xtag("state", *this))); + this->illegal_input_error(self_name, tk); } void - exprstate::on_singleassign_token(const token_type & /*tk*/, + exprstate::on_singleassign_token(const token_type & tk, exprstatestack * /*p_stack*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); constexpr const char * self_name = "exprstate::on_singleassign_token"; - throw std::runtime_error(tostr(self_name, - ": unexpected equals for parsing state", - xtag("state", *this))); + this->illegal_input_error(self_name, tk); } void From fa335ee523cdc1b54075b9da2bfc7d75d21704b7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 14 Aug 2024 15:44:08 -0400 Subject: [PATCH 1383/2524] xo-tokenizer: feat: + basic arithmetic operators --- include/xo/tokenizer/span.hpp | 2 +- include/xo/tokenizer/tokenizer.hpp | 6 +++--- include/xo/tokenizer/tokentype.hpp | 14 ++++++++++++++ src/tokenizer/tokentype.cpp | 5 +++++ 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/include/xo/tokenizer/span.hpp b/include/xo/tokenizer/span.hpp index ca63759f..241f52aa 100644 --- a/include/xo/tokenizer/span.hpp +++ b/include/xo/tokenizer/span.hpp @@ -62,7 +62,7 @@ namespace xo { /** @brief create span representing prefix up to (but not including) @p *p **/ - span prefix(CharT * p) const { + span prefix_upto(CharT * p) const { if (p <= hi_) return span(lo_, p); else diff --git a/include/xo/tokenizer/tokenizer.hpp b/include/xo/tokenizer/tokenizer.hpp index 09bb4d97..11ee5aca 100644 --- a/include/xo/tokenizer/tokenizer.hpp +++ b/include/xo/tokenizer/tokenizer.hpp @@ -560,13 +560,13 @@ namespace xo { /* no-op */ return { token_type::invalid(), - input.prefix(ix) + input.prefix_upto(ix) }; } /* here: *ix is not whitespace */ - auto whitespace = input.prefix(ix); + auto whitespace = input.prefix_upto(ix); log && log(xtag("whitespace.size", whitespace.size())); @@ -630,7 +630,7 @@ namespace xo { } } - auto token_span = input.after_prefix(whitespace).prefix(ix); + auto token_span = input.after_prefix(whitespace).prefix_upto(ix); token tk = (this->prefix_.empty() diff --git a/include/xo/tokenizer/tokentype.hpp b/include/xo/tokenizer/tokentype.hpp index bd20e8eb..6da013d9 100644 --- a/include/xo/tokenizer/tokentype.hpp +++ b/include/xo/tokenizer/tokentype.hpp @@ -106,6 +106,20 @@ namespace xo { /** '->' **/ tk_yields, + /** note: operators not treated as punctuation + * 'do-always' is a legal variable name, + * as is 'maybe*2', 'maybe+1', 'path/to/foo' + **/ + + /** operator '+' **/ + tk_plus, + /** operator '-' **/ + tk_minus, + /** operator '*' **/ + tk_star, + /** operator '/' **/ + tk_slash, + /** keyworkd 'type' **/ tk_type, diff --git a/src/tokenizer/tokentype.cpp b/src/tokenizer/tokentype.cpp index 08dbba84..b7172118 100644 --- a/src/tokenizer/tokentype.cpp +++ b/src/tokenizer/tokentype.cpp @@ -37,6 +37,11 @@ namespace xo { CASE(tk_assign); CASE(tk_yields); + CASE(tk_plus); + CASE(tk_minus); + CASE(tk_star); + CASE(tk_slash); + CASE(tk_type); CASE(tk_def); CASE(tk_lambda); From f677995f77a7a84f0aaeceb697f810195dc73292 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 14 Aug 2024 15:44:52 -0400 Subject: [PATCH 1384/2524] xo-reader: minor: comment --- include/xo/reader/exprstate.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index dc62e86f..ef65e8f5 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -144,6 +144,8 @@ namespace xo { return os; } + // ----- exprstatestack ----- + /** @class exprstatestack * @brief A stack of exprstate objects **/ From e149e85910c6968177233c156b03491b52e78653 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 14 Aug 2024 16:27:44 -0400 Subject: [PATCH 1385/2524] xo-tokenizer: recognize +,-,*,/ tokens --- include/xo/tokenizer/tokenizer.hpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/include/xo/tokenizer/tokenizer.hpp b/include/xo/tokenizer/tokenizer.hpp index 11ee5aca..f2eff1b8 100644 --- a/include/xo/tokenizer/tokenizer.hpp +++ b/include/xo/tokenizer/tokenizer.hpp @@ -147,6 +147,10 @@ namespace xo { case '+': /* can't be punctuation -- can appear inside f64 token */ return false; + case '*': + case '/': + /* not punctuation -- for symmetry with +,- */ + return false; case '.': /* can't be punctuation -- can appear inside f64 token */ return false; @@ -179,6 +183,16 @@ namespace xo { switch (*ix) { case '-': case '+': + if (token_text.size() == 1) { + /* standalone '+' or '-' */ + if (*ix == '+') + tk_type = tokentype::tk_plus; + else if(*ix == '-') + tk_type = tokentype::tk_minus; + } + + /** fall through to numeric literal code below **/ + ; case '.': case '0': case '1': @@ -306,6 +320,18 @@ namespace xo { break; } + case '*': + if (token_text.size() == 1) { + /* standalone '*' */ + tk_type = tokentype::tk_star; + } + break; + case '/': + if (token_text.size() == 1) { + /* standalone '/' */ + tk_type = tokentype::tk_slash; + } + break; case '"': { log && log("recognize string-token"); From 29596a7c1dc3f91bd8719fbbb9d16c6e7e8ac11e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 14 Aug 2024 23:44:17 -0400 Subject: [PATCH 1386/2524] xo-reader: feat: parse infix exprs for +,-,*,/ operators --- include/xo/reader/exprstate.hpp | 4 ++ include/xo/reader/progress_xs.hpp | 21 +++++++ src/reader/exprstate.cpp | 20 ++++++ src/reader/progress_xs.cpp | 101 +++++++++++++++++++++++++++--- utest/reader.test.cpp | 3 +- 5 files changed, 141 insertions(+), 8 deletions(-) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index ef65e8f5..d109f280 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -121,6 +121,10 @@ namespace xo { virtual void on_rightparen_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); + /** handle incoming operator token **/ + virtual void on_operator_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); /** handle incoming floating-point-literal token **/ virtual void on_f64_token(const token_type & tk, exprstatestack * p_stack, diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 361e6d3d..9a7381c7 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -10,6 +10,18 @@ namespace xo { namespace scm { + /* represent an infix operator */ + enum class optype { + invalid = -1, + + op_add, + op_subtract, + op_multiply, + op_divide, + + n_optype + }; + /** @class progress_xs * @brief state machine for parsing a schematica runtime-value-expression **/ @@ -49,6 +61,12 @@ namespace xo { virtual void on_rightparen_token(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; + + /* entry point for an infix operator token */ + virtual void on_operator_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; + virtual void on_f64_token(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; @@ -58,6 +76,9 @@ namespace xo { private: /** populate an expression here **/ rp gen_expr_; + + /** infix operator, if supplied **/ + optype op_type_ = optype::invalid; }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index e7933d7c..1985aa7d 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -136,6 +136,19 @@ namespace xo { this->illegal_input_error(self_name, tk); } + void + exprstate::on_operator_token(const token_type & tk, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_operator_token"; + + this->illegal_input_error(self_name, tk); + } + void exprstate::on_f64_token(const token_type & tk, exprstatestack * /*p_stack*/, @@ -220,6 +233,13 @@ namespace xo { case tokentype::tk_assign: case tokentype::tk_yields: + case tokentype::tk_plus: + case tokentype::tk_minus: + case tokentype::tk_star: + case tokentype::tk_slash: + this->on_operator_token(tk, p_stack, p_emit_expr); + return; + case tokentype::tk_type: case tokentype::tk_lambda: case tokentype::tk_if: diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 4d78fb8a..241c5da4 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -1,8 +1,12 @@ /* @file progress_xs.cpp */ #include "progress_xs.hpp" +#include "expect_expr_xs.hpp" +#include "xo/expression/Apply.hpp" namespace xo { + using xo::ast::Apply; + namespace scm { std::unique_ptr progress_xs::make(rp valex) { @@ -28,12 +32,54 @@ namespace xo { } void - progress_xs::on_expr(ref::brw /*expr*/, - exprstatestack * /*p_stack*/, + progress_xs::on_expr(ref::brw expr, + exprstatestack * p_stack, rp * /*p_emit_expr*/) { + constexpr const char * c_self_name = "progress_xs::on_expr"; + + rp result; + /* consecutive expressions isn't legal */ - assert(false); + switch (op_type_) { + case optype::invalid: + throw std::runtime_error(tostr(c_self_name, + ": consecutive unseparated exprs not legal")); + + break; + + case optype::op_add: + result = Apply::make_add2_f64(this->gen_expr_ /*lhs*/, + expr.promote() /*rhs*/); + break; + + case optype::op_subtract: + result = Apply::make_sub2_f64(this->gen_expr_ /*lhs*/, + expr.promote() /*rhs*/); + break; + + case optype::op_multiply: + result = Apply::make_mul2_f64(this->gen_expr_ /*lhs*/, + expr.promote() /*rhs*/); + break; + + case optype::op_divide: + result = Apply::make_div2_f64(this->gen_expr_ /*lhs*/, + expr.promote() /*rhs*/); + break; + + case optype::n_optype: + /* unreachable */ + assert(false); + } + + assert(result.get()); + + /* this expression complete.. */ + std::unique_ptr self = p_stack->pop_exprstate(); + + /* ..but more operators could follow, so don't commit yet */ + p_stack->push_exprstate(progress_xs::make(result)); } void @@ -75,7 +121,7 @@ namespace xo { rp expr = this->gen_expr_; - std::unique_ptr self = p_stack->pop_exprstate(); /* NOT KOSHER. invalidates *this */ + std::unique_ptr self = p_stack->pop_exprstate(); p_stack->top_exprstate().on_expr(expr, p_stack, @@ -160,17 +206,58 @@ namespace xo { } + void + progress_xs::on_operator_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) + { + constexpr const char * c_self_name = "progress_xs::on_operator_token"; + + if (op_type_ == optype::invalid) { + switch(tk.tk_type()) { + case tokentype::tk_plus: + this->op_type_ = optype::op_add; + break; + case tokentype::tk_minus: + this->op_type_ = optype::op_subtract; + break; + case tokentype::tk_star: + this->op_type_ = optype::op_multiply; + break; + case tokentype::tk_slash: + this->op_type_ = optype::op_divide; + break; + default: + /* unreachable */ + assert(false); + exprstate::on_operator_token(tk, p_stack, p_emit_expr); + } + + /* infix operator must be followed by non-empty expression */ + p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + } else { + throw std::runtime_error(tostr(c_self_name, + ": expected expression following operator", + xtag("tk", tk))); + } + } + void progress_xs::on_f64_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + exprstatestack * p_stack, + rp * p_emit_expr) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); constexpr const char * self_name = "progress_xs::on_f64"; - this->illegal_input_error(self_name, tk); + if (this->op_type_ == optype::invalid) { + this->illegal_input_error(self_name, tk); + } else { + assert(false); + exprstate::on_f64_token(tk, p_stack, p_emit_expr); + } } void diff --git a/utest/reader.test.cpp b/utest/reader.test.cpp index 8aff0011..fb4a170d 100644 --- a/utest/reader.test.cpp +++ b/utest/reader.test.cpp @@ -14,7 +14,8 @@ namespace xo { std::vector s_testcase_v = { {"def foo : f64 = 3.14159265;"}, - {"def foo : f64 = (3.14159265);"} + {"def foo : f64 = (3.14159265);"}, + {"def foo : f64 = 2.0 * 3.14159265;"} }; } From bba6898ee15e7dcc3a8db683046ec99b7f48e5d3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 15 Aug 2024 00:14:55 -0400 Subject: [PATCH 1387/2524] xo-reader: fix+prep: setup for associative behaviore --- include/xo/reader/progress_xs.hpp | 28 +++++++- src/reader/progress_xs.cpp | 110 ++++++++++++++++++++++-------- 2 files changed, 106 insertions(+), 32 deletions(-) diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 9a7381c7..fb56c141 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -6,6 +6,7 @@ #pragma once #include "exprstate.hpp" +#include //#include namespace xo { @@ -22,6 +23,15 @@ namespace xo { n_optype }; + extern const char * + optype_descr(optype x); + + inline std::ostream & + operator<< (std::ostream & os, optype x) { + os << optype_descr(x); + return os; + } + /** @class progress_xs * @brief state machine for parsing a schematica runtime-value-expression **/ @@ -74,11 +84,25 @@ namespace xo { virtual void print(std::ostream & os) const override; private: - /** populate an expression here **/ - rp gen_expr_; + /** assemble expression representing + * value of + * @code + * f(lhs_, rhs_) + * @endcode + * + * where f determined by @ref op_type_ + **/ + rp assemble_expr(); + + private: + /** populate an expression here, may be followed by an operator **/ + rp lhs_; /** infix operator, if supplied **/ optype op_type_ = optype::invalid; + + /** populate an expression here, following infix operator */ + rp rhs_; }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 241c5da4..b7a1775b 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -5,9 +5,29 @@ #include "xo/expression/Apply.hpp" namespace xo { + using xo::ast::Expression; using xo::ast::Apply; namespace scm { + const char * + optype_descr(optype x) { + switch (x) { + case optype::invalid: + return "?optype"; + case optype::op_add: + return "op+"; + case optype::op_subtract: + return "op-"; + case optype::op_multiply: + return "op*"; + case optype::op_divide: + return "op/"; + case optype::n_optype: + break; + } + return "???"; + } + std::unique_ptr progress_xs::make(rp valex) { return std::make_unique(progress_xs(std::move(valex))); @@ -15,7 +35,7 @@ namespace xo { progress_xs::progress_xs(rp valex) : exprstate(exprstatetype::expr_progress), - gen_expr_{std::move(valex)} + lhs_{std::move(valex)} {} bool @@ -31,48 +51,66 @@ namespace xo { this->illegal_input_error(self_name, tk) ; } - void - progress_xs::on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) - { - constexpr const char * c_self_name = "progress_xs::on_expr"; + rp + progress_xs::assemble_expr() { + /* need to defer building Apply incase expr followed by higher-precedence operator: + * consider input like + * 3.14 + 2.0 * ... + */ - rp result; - - /* consecutive expressions isn't legal */ + /* consecutive expressions not legal, e.g: + * 3.14 6.28 + * but expressions surrounding an infix operators is: + * 3.14 / 6.28 + */ switch (op_type_) { case optype::invalid: - throw std::runtime_error(tostr(c_self_name, - ": consecutive unseparated exprs not legal")); - - break; + return this->lhs_; case optype::op_add: - result = Apply::make_add2_f64(this->gen_expr_ /*lhs*/, - expr.promote() /*rhs*/); - break; + return Apply::make_add2_f64(this->lhs_, + this->rhs_); case optype::op_subtract: - result = Apply::make_sub2_f64(this->gen_expr_ /*lhs*/, - expr.promote() /*rhs*/); - break; + return Apply::make_sub2_f64(this->lhs_, + this->rhs_); case optype::op_multiply: - result = Apply::make_mul2_f64(this->gen_expr_ /*lhs*/, - expr.promote() /*rhs*/); - break; + return Apply::make_mul2_f64(this->lhs_, + this->rhs_); case optype::op_divide: - result = Apply::make_div2_f64(this->gen_expr_ /*lhs*/, - expr.promote() /*rhs*/); - break; + return Apply::make_div2_f64(this->lhs_, + this->rhs_); case optype::n_optype: /* unreachable */ assert(false); + return nullptr; } + return nullptr; + } + + void + progress_xs::on_expr(ref::brw expr, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + /* note: previous token probably an operator, + * handled from progress_xs::on_operator_token(), + * which pushes expect_expr_xs::expect_rhs_expression() + */ + + constexpr const char * c_self_name = "progress_xs::on_expr"; + + + if (op_type_ == optype::invalid) { + throw std::runtime_error(tostr(c_self_name, + ": consecutive unseparated exprs not legal")); + } + +#ifdef NOT_QUITE assert(result.get()); /* this expression complete.. */ @@ -80,6 +118,9 @@ namespace xo { /* ..but more operators could follow, so don't commit yet */ p_stack->push_exprstate(progress_xs::make(result)); +#endif + + this->rhs_ = expr.promote(); } void @@ -116,10 +157,12 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr) { + /* note: implementation parllels .on_rightparen_token() */ + constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - rp expr = this->gen_expr_; + rp expr = this->assemble_expr(); std::unique_ptr self = p_stack->pop_exprstate(); @@ -171,6 +214,9 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr) { + /* note: implementation parallels .on_semicolon_token() */ + + constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -188,7 +234,7 @@ namespace xo { */ /* right paren confirms stack expression */ - rp expr = this->gen_expr_; + rp expr = this->assemble_expr(); std::unique_ptr self = p_stack->pop_exprstate(); @@ -264,8 +310,12 @@ namespace xo { progress_xs::print(std::ostream & os) const { os << ""; } From c36e8cae40edc0a011df1cd4594d7aa7c6962ec3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 15 Aug 2024 00:36:02 -0400 Subject: [PATCH 1388/2524] xo-reader: feat: support operator precedence for *,/ over +,- --- include/xo/reader/progress_xs.hpp | 8 +- src/reader/progress_xs.cpp | 132 ++++++++++++++++++++++++------ 2 files changed, 114 insertions(+), 26 deletions(-) diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index fb56c141..aca1977e 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -26,6 +26,9 @@ namespace xo { extern const char * optype_descr(optype x); + extern int + precedence(optype x); + inline std::ostream & operator<< (std::ostream & os, optype x) { os << optype_descr(x); @@ -37,12 +40,13 @@ namespace xo { **/ class progress_xs : public exprstate { public: - progress_xs(rp valex); + progress_xs(rp valex, optype op); virtual ~progress_xs() = default; static const progress_xs * from(const exprstate * x) { return dynamic_cast(x); } - static std::unique_ptr make(rp valex); + static std::unique_ptr make(rp valex, + optype optype = optype::invalid); bool admits_f64() const; diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index b7a1775b..421524a2 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -28,14 +28,34 @@ namespace xo { return "???"; } - std::unique_ptr - progress_xs::make(rp valex) { - return std::make_unique(progress_xs(std::move(valex))); + int + precedence(optype x) { + switch (x) { + case optype::invalid: + case optype::n_optype: + return 0; + + case optype::op_add: + case optype::op_subtract: + return 1; + + case optype::op_multiply: + case optype::op_divide: + return 2; + } + + return 0; } - progress_xs::progress_xs(rp valex) + std::unique_ptr + progress_xs::make(rp valex, optype op) { + return std::make_unique(progress_xs(std::move(valex), op)); + } + + progress_xs::progress_xs(rp valex, optype op) : exprstate(exprstatetype::expr_progress), - lhs_{std::move(valex)} + lhs_{std::move(valex)}, + op_type_{op} {} bool @@ -58,6 +78,15 @@ namespace xo { * 3.14 + 2.0 * ... */ + constexpr const char * c_self_name = "progress_xs::assemble_expr"; + + if ((op_type_ != optype::invalid) && (rhs_.get() == nullptr)) { + throw std::runtime_error(tostr(c_self_name, + ": expected expr on rhs of operator", + xtag("lhs", lhs_), + xtag("op", op_type_))); + } + /* consecutive expressions not legal, e.g: * 3.14 6.28 * but expressions surrounding an infix operators is: @@ -252,35 +281,90 @@ namespace xo { } + namespace { + optype + tk2op(const tokentype & tktype) { + switch (tktype) { + case tokentype::tk_plus: + return optype::op_add; + case tokentype::tk_minus: + return optype::op_subtract; + case tokentype::tk_star: + return optype::op_multiply; + case tokentype::tk_slash: + return optype::op_divide; + default: + assert(false); + return optype::invalid; + } + return optype::invalid; + } + } + void progress_xs::on_operator_token(const token_type & tk, exprstatestack * p_stack, - rp * p_emit_expr) + rp * /*p_emit_expr*/) { constexpr const char * c_self_name = "progress_xs::on_operator_token"; if (op_type_ == optype::invalid) { - switch(tk.tk_type()) { - case tokentype::tk_plus: - this->op_type_ = optype::op_add; - break; - case tokentype::tk_minus: - this->op_type_ = optype::op_subtract; - break; - case tokentype::tk_star: - this->op_type_ = optype::op_multiply; - break; - case tokentype::tk_slash: - this->op_type_ = optype::op_divide; - break; - default: - /* unreachable */ - assert(false); - exprstate::on_operator_token(tk, p_stack, p_emit_expr); - } + this->op_type_ = tk2op(tk.tk_type()); /* infix operator must be followed by non-empty expression */ p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + } else if (rhs_) { + /* already have complete expression stashed. + * behavior depends on operator precedence for tk with stored operator + * this->op_type_ + */ + optype op2 = tk2op(tk.tk_type()); + + if (precedence(op2) <= precedence(this->op_type_)) { + /* e.g. + * 6.2 * 4.9 + ... + * + * in stack: + * 1. progress_xs lhs=6.2, op=*, rhs=4.9 + * + * out stack + * 1. progress_xs lhs=apply(*,6.2,4.9), op=+ + */ + + /* 1. instantiate expression for *this */ + auto expr = this->assemble_expr(); + + /* 2. remove from stack */ + std::unique_ptr self = p_stack->pop_exprstate(); + + /* 3. replace with new progress_xs: */ + p_stack->push_exprstate(progress_xs::make(expr, op2)); + + /* infix operator must be followed by non-empty expression */ + p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + } else { + /* e.g. + * 6.2 + 4.9 * ... + * + * in stack: + * 1. progress_xs lhs=6.2, op=+, rhs=4.9 + * + * out stack: + * 1. progress_xs lhs=6.2, op=+ + * 2. expect_rhs_expression + * 3. progress_xs lhs=4.9, op=* + * 4. expect_rhs_expression + */ + + std::unique_ptr self = p_stack->pop_exprstate(); + + /* 1. replace with nested incomplete infix exprs */ + p_stack->push_exprstate(progress_xs::make(lhs_, op_type_)); + p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + p_stack->push_exprstate(progress_xs::make(rhs_, op2)); + p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + } + } else { throw std::runtime_error(tostr(c_self_name, ": expected expression following operator", From 5b53dbeac7ba5d1d2a743c7539d14f5605395c02 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 15 Aug 2024 14:00:35 -0400 Subject: [PATCH 1389/2524] xo-reader: wip: stub for lambda expression --- include/xo/reader/lambda_xs.hpp | 26 ++++++++++++++++++++++++++ src/reader/CMakeLists.txt | 3 ++- src/reader/lambda_xs.cpp | 13 +++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 include/xo/reader/lambda_xs.hpp create mode 100644 src/reader/lambda_xs.cpp diff --git a/include/xo/reader/lambda_xs.hpp b/include/xo/reader/lambda_xs.hpp new file mode 100644 index 00000000..4c72efe9 --- /dev/null +++ b/include/xo/reader/lambda_xs.hpp @@ -0,0 +1,26 @@ +/** @file lambda_xs.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "exprstate.hpp" +//#include + +namespace xo { + namespace scm { + /** @class lambda_xs + * @brief parsing state-machine for a lambda-expression + **/ + class lambda_xs : public exprstate { + public: + lambda_xs(); + + private: + }; + } /*namespace scm*/ +} /*namespace xo*/ + + +/** end lambda_xs.hpp **/ diff --git a/src/reader/CMakeLists.txt b/src/reader/CMakeLists.txt index 6907dff2..faf190f3 100644 --- a/src/reader/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -11,7 +11,8 @@ set(SELF_SRCS exprseq_xs.cpp expect_expr_xs.cpp expect_symbol_xs.cpp - expect_type_xs.cpp) + expect_type_xs.cpp + lambda_xs.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_dependency(${SELF_LIB} xo_expression) diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp new file mode 100644 index 00000000..603f3585 --- /dev/null +++ b/src/reader/lambda_xs.cpp @@ -0,0 +1,13 @@ +/* @file lambda_xs.cpp */ + +#include "lambda_xs.hpp" + +namespace xo { + namespace scm { + lambda_xs::lambda_xs() {} + + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end lambda_xs.cpp */ From 6d5387eef7f332d1cf8261346e5a152836f05cce Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 16 Aug 2024 21:55:23 -0400 Subject: [PATCH 1390/2524] xo-reader: + expect_formal_xs [wip - not used] --- include/xo/reader/define_xs.hpp | 8 +-- include/xo/reader/expect_formal_xs.hpp | 80 +++++++++++++++++++++++ include/xo/reader/exprstate.hpp | 6 +- include/xo/reader/formal_arg.hpp | 58 +++++++++++++++++ include/xo/reader/progress_xs.hpp | 4 +- src/reader/CMakeLists.txt | 1 + src/reader/expect_formal_xs.cpp | 87 ++++++++++++++++++++++++++ src/reader/exprstate.cpp | 50 +++++++++++++-- 8 files changed, 282 insertions(+), 12 deletions(-) create mode 100644 include/xo/reader/expect_formal_xs.hpp create mode 100644 include/xo/reader/formal_arg.hpp create mode 100644 src/reader/expect_formal_xs.cpp diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index f3a5f15a..59b55d83 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -56,18 +56,18 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr) override; virtual void on_typedescr(TypeDescr td, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) override; + exprstatestack * p_stack, + rp * p_emit_expr) override; virtual void on_colon_token(const token_type & tk, exprstatestack * p_stack) override; virtual void on_semicolon_token(const token_type & tk, exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + rp * p_emit_expr) override; virtual void on_singleassign_token(const token_type & tk, exprstatestack * p_stack) override; virtual void on_rightparen_token(const token_type & tk, exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + rp * p_emit_expr) override; virtual void on_f64_token(const token_type & tk, exprstatestack * p_stack, rp * /*p_emit_expr*/) override; diff --git a/include/xo/reader/expect_formal_xs.hpp b/include/xo/reader/expect_formal_xs.hpp new file mode 100644 index 00000000..557ffa61 --- /dev/null +++ b/include/xo/reader/expect_formal_xs.hpp @@ -0,0 +1,80 @@ +/* file expect_formal_xs.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "exprstate.hpp" +#include "formal_arg.hpp" + +namespace xo { + namespace scm { + /** + * name : type + * ^ ^ ^ + * | | formal_2 + * | formal_1 + * formal_0 + * + **/ + enum class formalstatetype { + invalid = -1, + + formal_0, + formal_1, + formal_2, + + n_formalstatetype, + }; + + extern const char * + formalstatetype_descr(formalstatetype x); + + inline std::ostream & + operator<< (std::ostream & os, formalstatetype x) { + os << formalstatetype_descr(x); + return os; + } + + /** @class expect_formal_xs + * @brief parser state-machine for a typed formal parameter + **/ + class expect_formal_xs : public exprstate { + public: + expect_formal_xs() = default; + + virtual void on_symbol(const std::string & symbol_name, + exprstatestack * p_stack, + rp * p_emit_expr) override; + + virtual void on_colon_token(const token_type & tk, + exprstatestack * p_stack + /*rp * p_emit_expr*/) override; + + // virtual void on_comma_token(...) override; + +#ifdef PROBABLY_NOT + virtual void on_rightparen_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; +#endif + + virtual void on_typedescr(TypeDescr td, + exprstatestack * p_stack, + rp * p_emit_expr) override; + + virtual void print(std::ostream & os) const override; + + private: + /** parsing state-machine state **/ + formalstatetype formalxs_type_; + /** populate with {parameter-name, parameter-type} + * as they're encountered + **/ + formal_arg result_; + }; + } /*namespace scm*/ +} /*namespace xo*/ + +/* end expect_formal_xs.hpp */ diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index ef65e8f5..fbdc8b50 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -52,7 +52,7 @@ namespace xo { class exprstatestack; - class define_xs; + class formal_arg; /** state associated with a partially-parsed expression. **/ @@ -91,6 +91,10 @@ namespace xo { virtual void on_typedescr(TypeDescr td, exprstatestack * p_stack, rp * p_emit_expr); + /** update exprstate when expecting a formal parameter **/ + virtual void on_formal(const formal_arg & formal, + exprstatestack * p_stack, + rp * p_emit_expr); /** print human-readable representation on @p os **/ virtual void print(std::ostream & os) const; diff --git a/include/xo/reader/formal_arg.hpp b/include/xo/reader/formal_arg.hpp new file mode 100644 index 00000000..4f0afaef --- /dev/null +++ b/include/xo/reader/formal_arg.hpp @@ -0,0 +1,58 @@ +/* file formal_arg.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "TypeDescr.hpp" +#include "xo/indentlog/print/tag.hpp" + +namespace xo { + namespace scm { + /** @class formal_arg + * @brief description of formal parameter in an argument list + * + * Terminated by an argument separator ',' or rightparen ')' + **/ + class formal_arg { + public: + using TypeDescr = xo::reflect::TypeDescr; + + public: + formal_arg() = default; + formal_arg(const std::string & n, TypeDescr td) : name_{n}, td_{td} {} + + const std::string & name() const { return name_; } + TypeDescr td() const { return td_; } + + void assign_name(const std::string & x) { name_ = x; } + void assign_td(TypeDescr x) { td_ = x; } + + void print(std::ostream & os) const { + os << ""; + } + + private: + /** formal parameter name **/ + std::string name_; + /** type description for variable @p name **/ + TypeDescr td_; + }; + + inline std::ostream & + operator<< (std::ostream & os, + const formal_arg & x) { + x.print(os); + return os; + } + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end formal_arg.hpp */ diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 361e6d3d..bc1ada8d 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -18,7 +18,9 @@ namespace xo { progress_xs(rp valex); virtual ~progress_xs() = default; - static const progress_xs * from(const exprstate * x) { return dynamic_cast(x); } + static const progress_xs * from(const exprstate * x) { + return dynamic_cast(x); + } static std::unique_ptr make(rp valex); diff --git a/src/reader/CMakeLists.txt b/src/reader/CMakeLists.txt index 6907dff2..ae233e22 100644 --- a/src/reader/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -11,6 +11,7 @@ set(SELF_SRCS exprseq_xs.cpp expect_expr_xs.cpp expect_symbol_xs.cpp + expect_formal_xs.cpp expect_type_xs.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp new file mode 100644 index 00000000..5ec91987 --- /dev/null +++ b/src/reader/expect_formal_xs.cpp @@ -0,0 +1,87 @@ +/* file expect_formal_xs.cpp + * + * author: Roland Conybeare + */ + +#include "expect_formal_xs.hpp" + +namespace xo { + using xo::reflect::TypeDescr; + + namespace scm{ + const char * + formalstatetype_descr(formalstatetype x) { + switch (x) { + case formalstatetype::invalid: + case formalstatetype::n_formalstatetype: + return "?formalstatetype"; + case formalstatetype::formal_0: + return "formal_0"; + case formalstatetype::formal_1: + return "formal_1"; + case formalstatetype::formal_2: + return "formal_2"; + } + + return "???formalstatetype"; + } + + void + expect_formal_xs::on_symbol(const std::string & symbol_name, + exprstatestack * p_stack, + rp * p_emit_expr) + { + if (this->formalxs_type_ == formalstatetype::formal_0) { + this->formalxs_type_ = formalstatetype::formal_1; + this->result_.assign_name(symbol_name); + } else { + exprstate::on_symbol(symbol_name, + p_stack, + p_emit_expr); + } + } + + void + expect_formal_xs::on_colon_token(const token_type & tk, + exprstatestack * p_stack + /* rp * p_emit_expr */) + { + if (this->formalxs_type_ == formalstatetype::formal_1) { + this->formalxs_type_ = formalstatetype::formal_2; + } else { + exprstate::on_colon_token(tk, + p_stack); + } + } + + void + expect_formal_xs::on_typedescr(TypeDescr td, + exprstatestack * p_stack, + rp * p_emit_expr) + { + if (this->formalxs_type_ == formalstatetype::formal_2) { + this->result_.assign_td(td); + + std::unique_ptr self = p_stack->pop_exprstate(); + + //p_stack->top_exprstate().on_formal(result_, p_stack, p_emit_expr); + } else { + exprstate::on_typedescr(td, p_stack, p_emit_expr); + } + } + + void + expect_formal_xs::print(std::ostream & os) const { + os << ""; + } + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end expect_formal_xs.cpp */ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index e7933d7c..d36fba94 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -1,6 +1,8 @@ /* @file exprstate.cpp */ #include "exprstate.hpp" +#include "formal_arg.hpp" +#include //#include "define_xs.hpp" //#include "progress_xs.hpp" //#include "paren_xs.hpp" @@ -63,15 +65,45 @@ namespace xo { } void - exprstate::on_typedescr(TypeDescr /*td*/, - exprstatestack * /*p_stack*/, + exprstate::on_typedescr(TypeDescr td, + exprstatestack * p_stack, rp * /*p_emit_expr*/) { /* returning type description to something that wants it */ - /* unreachable - implement in derived class */ - assert(false); - return; + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("exstype", + p_stack->top_exprstate().exs_type())); + + constexpr const char * c_self_name = "exprstate::on_typedescr"; + + throw std::runtime_error(tostr(c_self_name, + ": unexpected typedescr for parsing state", + xtag("td", td), + xtag("state", *this))); + } + + void + exprstate::on_formal(const formal_arg & formal, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) + { + /* returning type description to something that wants it */ + + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("exstype", + p_stack->top_exprstate().exs_type())); + + constexpr const char * c_self_name = "exprstate::on_formal"; + + throw std::runtime_error(tostr(c_self_name, + ": unexpected formal-arg for parsing state", + xtag("formal", formal), + xtag("state", *this))); } void @@ -254,13 +286,19 @@ namespace xo { } /*on_expr*/ void - exprstate::on_symbol(const std::string & /*symbol_name*/, + exprstate::on_symbol(const std::string & symbol_name, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) { /* unreachable - derived class that can receive * will override this method */ + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("exstype", this->exs_type_), + xtag("symbol_name", symbol_name)); + assert(false); } From 52d1bd87900f28bf88bdbda708617f028b3b8255 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 16 Aug 2024 22:15:58 -0400 Subject: [PATCH 1391/2524] xo-reader: + exprstate.on_comma_token() [wip, not used] --- include/xo/reader/exprstate.hpp | 4 ++++ src/reader/exprstate.cpp | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 87eaf29d..74d8f0dc 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -107,6 +107,10 @@ namespace xo { virtual void on_symbol_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr); + /** handle incoming ',' token **/ + virtual void on_comma_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); /** handle incoming ':' token **/ virtual void on_colon_token(const token_type & tk, exprstatestack * p_stack); diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 43504622..17c68eff 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -118,6 +118,19 @@ namespace xo { this->illegal_input_error(self_name, tk); } + void + exprstate::on_comma_token(const token_type & tk, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_comma"; + + this->illegal_input_error(self_name, tk); + } + void exprstate::on_semicolon_token(const token_type & tk, exprstatestack * /*p_stack*/, From 8ed090c2e20401e2dede2055e2def0de97446548 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 16 Aug 2024 22:17:39 -0400 Subject: [PATCH 1392/2524] xo-reader: dispatch comm token -> exprstate.on_comma_token() --- src/reader/exprstate.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 17c68eff..7e207da8 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -255,10 +255,13 @@ namespace xo { case tokentype::tk_leftangle: case tokentype::tk_rightangle: case tokentype::tk_dot: - case tokentype::tk_comma: assert(false); return; + case tokentype::tk_comma: + this->on_comma_token(tk, p_stack, p_emit_expr); + return; + case tokentype::tk_colon: this->on_colon_token(tk, p_stack); return; From 0841fd7dbd5c90e65e7e8d218e3b9dcfcc09b68d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 17 Aug 2024 01:09:17 -0400 Subject: [PATCH 1393/2524] xo-reader: wip: parsing lambda expressions [wip, non-functional] --- include/xo/reader/expect_expr_xs.hpp | 3 + .../xo/reader/expect_formal_arglist_xs.hpp | 55 +++++++++++++ include/xo/reader/exprstate.hpp | 11 ++- include/xo/reader/lambda_xs.hpp | 46 +++++++++++ src/reader/CMakeLists.txt | 2 + src/reader/expect_expr_xs.cpp | 18 +++++ src/reader/expect_formal_arglist_xs.cpp | 37 +++++++++ src/reader/expect_type_xs.cpp | 1 + src/reader/exprstate.cpp | 42 +++++++++- src/reader/lambda_xs.cpp | 79 +++++++++++++++++++ 10 files changed, 289 insertions(+), 5 deletions(-) create mode 100644 include/xo/reader/expect_formal_arglist_xs.hpp create mode 100644 src/reader/expect_formal_arglist_xs.cpp diff --git a/include/xo/reader/expect_expr_xs.hpp b/include/xo/reader/expect_expr_xs.hpp index fa6c4630..740eecc8 100644 --- a/include/xo/reader/expect_expr_xs.hpp +++ b/include/xo/reader/expect_expr_xs.hpp @@ -19,6 +19,9 @@ namespace xo { static std::unique_ptr expect_rhs_expression(); + virtual void on_lambda_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; virtual void on_leftparen_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) override; diff --git a/include/xo/reader/expect_formal_arglist_xs.hpp b/include/xo/reader/expect_formal_arglist_xs.hpp new file mode 100644 index 00000000..88b0adc1 --- /dev/null +++ b/include/xo/reader/expect_formal_arglist_xs.hpp @@ -0,0 +1,55 @@ +/* file expect_formal_arglist_xs.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "exprstate.hpp" +#include "formal_arg.hpp" +#include + +namespace xo { + namespace scm { + enum class formalarglstatetype { + invalid = -1, + + argl_0, + argl_1, + + n_formalarglstatetype, + }; + + /** @class expect_formal_arglist + * @brief parser state-machine for a formal parameter list + * + * ( name(1) : type(1), ..., ) + * ^ + * | + * argl_0 + **/ + class expect_formal_arglist_xs : public exprstate { + public: + expect_formal_arglist_xs(); + + static std::unique_ptr make(); + + const std::vector & argl() const { return argl_; } + + virtual void on_leftparen_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; + + private: + /** parsing state-machine state **/ + formalarglstatetype farglxs_type_; + /** populate with (parmaeter-name, parameter-type) list + * as they're encountered + **/ + std::vector argl_; + }; + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end expect_formal_arglist_xs.hpp */ diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 74d8f0dc..65dd944e 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -59,6 +59,7 @@ namespace xo { class exprstate { public: using Expression = xo::ast::Expression; + using Variable = xo::ast::Variable; using exprtype = xo::ast::exprtype; using token_type = token; using TypeDescr = xo::reflect::TypeDescr; @@ -92,9 +93,13 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr); /** update exprstate when expecting a formal parameter **/ - virtual void on_formal(const formal_arg & formal, + virtual void on_formal(const rp & formal, exprstatestack * p_stack, rp * p_emit_expr); + /** update expression when epecting a formal parameter list **/ + virtual void on_formal_arglist(const std::vector> & argl, + exprstatestack * p_stack, + rp * p_emit_expr); /** print human-readable representation on @p os **/ virtual void print(std::ostream & os) const; @@ -103,6 +108,10 @@ namespace xo { /** handle incoming 'def' token **/ virtual void on_def_token(const token_type & tk, exprstatestack * p_stack); + /** handle incoming 'lambda' token **/ + virtual void on_lambda_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr); /** handle incoming symbol token **/ virtual void on_symbol_token(const token_type & tk, exprstatestack * p_stack, diff --git a/include/xo/reader/lambda_xs.hpp b/include/xo/reader/lambda_xs.hpp index 4c72efe9..4b4fb74c 100644 --- a/include/xo/reader/lambda_xs.hpp +++ b/include/xo/reader/lambda_xs.hpp @@ -10,14 +10,60 @@ namespace xo { namespace scm { + /** + * lambda ( name(1) : type(1), ..., ) body-expr ; + * ^ ^ ^ ^ + * | | | | + * lm_0 lm_1 lm_2 lm_3 + * + * lm_0 --on_lambda_token()--> lm_1 + * lm_1 --on_formal_arglist()--> lm_2 + * lm_2 --on_expr()--> lm_3 + * lm_3 --on_semicolon_token()--> (done) + **/ + enum class lambdastatetype { + invalid = -1, + + lm_0, + lm_1, + lm_2, + lm_3, + + n_lambdastatetype + }; + /** @class lambda_xs * @brief parsing state-machine for a lambda-expression + * **/ class lambda_xs : public exprstate { public: lambda_xs(); + static std::unique_ptr make(); + + virtual void on_lambda_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; + virtual void on_formal_arglist(const std::vector> & argl, + exprstatestack * p_stack, + rp * p_emit_expr) override; + virtual void on_expr(ref::brw expr, + exprstatestack * p_stack, + rp * p_emit_expr) override; + virtual void on_semicolon_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; + private: + /** parsing state-machine state **/ + lambdastatetype lmxs_type_; + + /** formal parameter list **/ + std::vector> argl_; + + /** body expression **/ + rp body_; }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/CMakeLists.txt b/src/reader/CMakeLists.txt index 5be9eb8a..32a4cd5a 100644 --- a/src/reader/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -11,7 +11,9 @@ set(SELF_SRCS exprseq_xs.cpp expect_expr_xs.cpp expect_symbol_xs.cpp + expect_formal_xs.cpp + expect_formal_arglist_xs.cpp expect_type_xs.cpp lambda_xs.cpp) diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index 131bfc42..fe4a1830 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -4,8 +4,10 @@ */ #include "expect_expr_xs.hpp" +#include "lambda_xs.hpp" #include "paren_xs.hpp" #include "progress_xs.hpp" +#include "xo/expression/Lambda.hpp" #include "xo/expression/Constant.hpp" namespace xo { @@ -23,6 +25,22 @@ namespace xo { : exprstate(exprstatetype::expect_rhs_expression) {} + void + expect_expr_xs::on_lambda_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + //constexpr const char * self_name = "exprstate::on_leftparen"; + + /* push lparen_0 to remember to look for subsequent rightparen. */ + p_stack->push_exprstate(lambda_xs::make()); + p_stack->top_exprstate().on_lambda_token(tk, p_stack, p_emit_expr); + //p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + } + void expect_expr_xs::on_leftparen_token(const token_type & /*tk*/, exprstatestack * p_stack, diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp new file mode 100644 index 00000000..c540dbd3 --- /dev/null +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -0,0 +1,37 @@ +/* file expect_formal_arglist_xs.cpp + * + * author: Roland Conybeare + */ + +#include "expect_formal_arglist_xs.hpp" + +namespace xo { + namespace scm { + std::unique_ptr + expect_formal_arglist_xs::make() { + return std::make_unique + (expect_formal_arglist_xs()); + } + + expect_formal_arglist_xs::expect_formal_arglist_xs() + : farglxs_type_{formalarglstatetype::argl_0} + {} + + void + expect_formal_arglist_xs::on_leftparen_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) + { + if (farglxs_type_ == formalarglstatetype::argl_0) { + this->farglxs_type_ = formalarglstatetype::argl_1; + return; + } + + exprstate::on_leftparen_token(tk, p_stack, p_emit_expr); + } + + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end expect_formal_arglist_xs.cpp */ diff --git a/src/reader/expect_type_xs.cpp b/src/reader/expect_type_xs.cpp index 6c4c5afb..fa4a517a 100644 --- a/src/reader/expect_type_xs.cpp +++ b/src/reader/expect_type_xs.cpp @@ -11,6 +11,7 @@ namespace xo { using xo::reflect::Reflect; namespace scm { + std::unique_ptr expect_type_xs::make() { return std::make_unique(expect_type_xs()); diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 7e207da8..859d0be8 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -1,7 +1,9 @@ /* @file exprstate.cpp */ #include "exprstate.hpp" -#include "formal_arg.hpp" +//#include "formal_arg.hpp" +#include "xo/expression/Variable.hpp" +#include "xo/indentlog/print/vector.hpp" #include //#include "define_xs.hpp" //#include "progress_xs.hpp" @@ -49,6 +51,14 @@ namespace xo { this->illegal_input_error("exprstate::on_def_token", tk); } + void + exprstate::on_lambda_token(const token_type & tk, + exprstatestack * /*p_stack*/, + rp * /*p_emit_expr*/) + { + this->illegal_input_error("exprstate::on_lambda_token", tk); + } + void exprstate::on_symbol_token(const token_type & tk, exprstatestack * p_stack, @@ -86,7 +96,7 @@ namespace xo { } void - exprstate::on_formal(const formal_arg & formal, + exprstate::on_formal(const rp & formal, exprstatestack * p_stack, rp * /*p_emit_expr*/) { @@ -102,7 +112,28 @@ namespace xo { throw std::runtime_error(tostr(c_self_name, ": unexpected formal-arg for parsing state", - xtag("formal", formal), + xtag("formal", formal.get()), + xtag("state", *this))); + } + + void + exprstate::on_formal_arglist(const std::vector> & argl, + exprstatestack * p_stack, + rp * /*p_emit_expr*/) + { + /* returning type description to something that wants it */ + + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("exstype", + p_stack->top_exprstate().exs_type())); + + constexpr const char * c_self_name = "exprstate::on_formal_arglist"; + + throw std::runtime_error(tostr(c_self_name, + ": unexpected formal-arg for parsing state", + xtag("argl", argl), xtag("state", *this))); } @@ -223,6 +254,10 @@ namespace xo { this->on_def_token(tk, p_stack); return; + case tokentype::tk_lambda: + this->on_lambda_token(tk, p_stack, p_emit_expr); + return; + case tokentype::tk_i64: assert(false); return; @@ -289,7 +324,6 @@ namespace xo { return; case tokentype::tk_type: - case tokentype::tk_lambda: case tokentype::tk_if: case tokentype::tk_let: diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index 603f3585..e38d5718 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -1,11 +1,90 @@ /* @file lambda_xs.cpp */ #include "lambda_xs.hpp" +#include "expect_formal_arglist_xs.hpp" +#include "expect_expr_xs.hpp" +#include "xo/expression/Lambda.hpp" namespace xo { + using xo::ast::Lambda; + namespace scm { + std::unique_ptr + lambda_xs::make() { + return std::make_unique(lambda_xs()); + } + lambda_xs::lambda_xs() {} + void + lambda_xs::on_lambda_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) + { + if (lmxs_type_ == lambdastatetype::lm_0) { + this->lmxs_type_ = lambdastatetype::lm_1; + p_stack->push_exprstate(expect_formal_arglist_xs::make()); + return; + } + + exprstate::on_lambda_token(tk, + p_stack, + p_emit_expr); + } + + void + lambda_xs::on_formal_arglist(const std::vector> & argl, + exprstatestack * p_stack, + rp * p_emit_expr) + { + if (lmxs_type_ == lambdastatetype::lm_1) { + this->lmxs_type_ = lambdastatetype::lm_2; + this->argl_ = argl; + p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + return; + } + + exprstate::on_formal_arglist(argl, + p_stack, + p_emit_expr); + } + + void + lambda_xs::on_expr(ref::brw expr, + exprstatestack * p_stack, + rp * p_emit_expr) + { + if (lmxs_type_ == lambdastatetype::lm_2) { + this->lmxs_type_ = lambdastatetype::lm_3; + this->body_ = expr.promote(); + return; + } + + exprstate::on_expr(expr, p_stack, p_emit_expr); + } + + void + lambda_xs::on_semicolon_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) + { + if (lmxs_type_ == lambdastatetype::lm_3) { + /* done! */ + + std::unique_ptr self = p_stack->pop_exprstate(); + + std::string name = "fixmename"; + + rp lm = Lambda::make(name, argl_, body_); + + p_stack->top_exprstate().on_expr(lm, p_stack, p_emit_expr); + p_stack->top_exprstate().on_semicolon_token(tk, p_stack, p_emit_expr); + + return; + } + + exprstate::on_semicolon_token(tk, p_stack, p_emit_expr); + } } /*namespace scm*/ } /*namespace xo*/ From 1628d8f44cea04497e351fff4e36fe5391ba6475 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 17 Aug 2024 13:26:57 -0400 Subject: [PATCH 1394/2524] xo-reader: feat: mvp lambda parsing [untested] --- .../xo/reader/expect_formal_arglist_xs.hpp | 47 ++++++++--- include/xo/reader/expect_formal_xs.hpp | 7 +- src/reader/expect_formal_arglist_xs.cpp | 78 ++++++++++++++++++- src/reader/expect_formal_xs.cpp | 12 ++- src/reader/lambda_xs.cpp | 19 ++--- 5 files changed, 135 insertions(+), 28 deletions(-) diff --git a/include/xo/reader/expect_formal_arglist_xs.hpp b/include/xo/reader/expect_formal_arglist_xs.hpp index 88b0adc1..501d945f 100644 --- a/include/xo/reader/expect_formal_arglist_xs.hpp +++ b/include/xo/reader/expect_formal_arglist_xs.hpp @@ -11,34 +11,63 @@ namespace xo { namespace scm { + /** + * ( name(1) : type(1) , ..., ) + * ^ ^ ^ ^ ^ + * | | | | | + * | | | | argl_1b + * | argl_1a | argla + * argl_0 argl_1b + * + * argl_0 --on_leftparen_token()--> argl_1a + * argl_1a --on_formal()--> argl_1b + * argl_1b -+-on_comma_token()--> argl_1a + * \-on_rightparen_token()--> (done) + **/ enum class formalarglstatetype { invalid = -1, argl_0, - argl_1, + argl_1a, + argl_1b, n_formalarglstatetype, }; + extern const char * + formalarglstatetype_descr(formalarglstatetype x); + + inline std::ostream & + operator<< (std::ostream & os, formalarglstatetype x) { + os << formalarglstatetype_descr(x); + return os; + } + /** @class expect_formal_arglist * @brief parser state-machine for a formal parameter list - * - * ( name(1) : type(1), ..., ) - * ^ - * | - * argl_0 **/ class expect_formal_arglist_xs : public exprstate { + public: + using Variable = xo::ast::Variable; + public: expect_formal_arglist_xs(); static std::unique_ptr make(); - const std::vector & argl() const { return argl_; } - virtual void on_leftparen_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) override; + virtual void on_formal(const rp & formal, + exprstatestack * p_stack, + rp * p_emit_expr) override; + virtual void on_comma_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; + virtual void on_rightparen_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) override; + virtual void print(std::ostream & os) const override; private: /** parsing state-machine state **/ @@ -46,7 +75,7 @@ namespace xo { /** populate with (parmaeter-name, parameter-type) list * as they're encountered **/ - std::vector argl_; + std::vector> argl_; }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/include/xo/reader/expect_formal_xs.hpp b/include/xo/reader/expect_formal_xs.hpp index 557ffa61..b4c046b9 100644 --- a/include/xo/reader/expect_formal_xs.hpp +++ b/include/xo/reader/expect_formal_xs.hpp @@ -17,6 +17,9 @@ namespace xo { * | formal_1 * formal_0 * + * formal_0 --on_symbol()--> formal_1 + * formal_1 --on_colon_token()--> formal_2 + * formal_2 --on_typedescr()--> (done) **/ enum class formalstatetype { invalid = -1, @@ -44,6 +47,8 @@ namespace xo { public: expect_formal_xs() = default; + static std::unique_ptr make(); + virtual void on_symbol(const std::string & symbol_name, exprstatestack * p_stack, rp * p_emit_expr) override; @@ -68,7 +73,7 @@ namespace xo { private: /** parsing state-machine state **/ - formalstatetype formalxs_type_; + formalstatetype formalxs_type_ = formalstatetype::formal_0; /** populate with {parameter-name, parameter-type} * as they're encountered **/ diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index c540dbd3..473cfb0c 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -4,9 +4,30 @@ */ #include "expect_formal_arglist_xs.hpp" +#include "expect_formal_xs.hpp" +#include "xo/expression/Variable.hpp" +#include "xo/indentlog/print/vector.hpp" namespace xo { namespace scm { + const char * + formalarglstatetype_descr(formalarglstatetype x) { + switch (x) { + case formalarglstatetype::invalid: + return "invalid"; + case formalarglstatetype::argl_0: + return "argl_0"; + case formalarglstatetype::argl_1a: + return "argl_1a"; + case formalarglstatetype::argl_1b: + return "argl_1b"; + case formalarglstatetype::n_formalarglstatetype: + break; + } + + return "?formalarglstatetype"; + } + std::unique_ptr expect_formal_arglist_xs::make() { return std::make_unique @@ -23,13 +44,62 @@ namespace xo { rp * p_emit_expr) { if (farglxs_type_ == formalarglstatetype::argl_0) { - this->farglxs_type_ = formalarglstatetype::argl_1; - return; + this->farglxs_type_ = formalarglstatetype::argl_1a; + p_stack->push_exprstate(expect_formal_xs::make()); + } else { + exprstate::on_leftparen_token(tk, p_stack, p_emit_expr); } - - exprstate::on_leftparen_token(tk, p_stack, p_emit_expr); } + void + expect_formal_arglist_xs::on_formal(const rp & formal, + exprstatestack * p_stack, + rp * p_emit_expr) + { + if (farglxs_type_ == formalarglstatetype::argl_1a) { + this->farglxs_type_ = formalarglstatetype::argl_1b; + this->argl_.push_back(formal); + } else { + exprstate::on_formal(formal, p_stack, p_emit_expr); + } + } + + void + expect_formal_arglist_xs::on_comma_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) + { + if (farglxs_type_ == formalarglstatetype::argl_1b) { + this->farglxs_type_ = formalarglstatetype::argl_1a; + p_stack->push_exprstate(expect_formal_xs::make()); + } else { + exprstate::on_comma_token(tk, p_stack, p_emit_expr); + } + } + + void + expect_formal_arglist_xs::on_rightparen_token(const token_type & tk, + exprstatestack * p_stack, + rp * p_emit_expr) + { + if (farglxs_type_ == formalarglstatetype::argl_1b) { + std::unique_ptr self = p_stack->pop_exprstate(); + + p_stack->top_exprstate().on_formal_arglist(this->argl_, + p_stack, p_emit_expr); + } else { + exprstate::on_rightparen_token(tk, p_stack, p_emit_expr); + } + } + + void + expect_formal_arglist_xs::print(std::ostream & os) const { + os << ""; + } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp index 5ec91987..9d69b137 100644 --- a/src/reader/expect_formal_xs.cpp +++ b/src/reader/expect_formal_xs.cpp @@ -4,8 +4,10 @@ */ #include "expect_formal_xs.hpp" +#include "xo/expression/Variable.hpp" namespace xo { + using xo::ast::Variable; using xo::reflect::TypeDescr; namespace scm{ @@ -26,6 +28,11 @@ namespace xo { return "???formalstatetype"; } + std::unique_ptr + expect_formal_xs::make() { + return std::make_unique(expect_formal_xs()); + } + void expect_formal_xs::on_symbol(const std::string & symbol_name, exprstatestack * p_stack, @@ -64,7 +71,10 @@ namespace xo { std::unique_ptr self = p_stack->pop_exprstate(); - //p_stack->top_exprstate().on_formal(result_, p_stack, p_emit_expr); + rp var = Variable::make(result_.name(), + result_.td()); + + p_stack->top_exprstate().on_formal(var, p_stack, p_emit_expr); } else { exprstate::on_typedescr(td, p_stack, p_emit_expr); } diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index e38d5718..a4a19845 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -24,12 +24,9 @@ namespace xo { if (lmxs_type_ == lambdastatetype::lm_0) { this->lmxs_type_ = lambdastatetype::lm_1; p_stack->push_exprstate(expect_formal_arglist_xs::make()); - return; + } else { + exprstate::on_lambda_token(tk, p_stack, p_emit_expr); } - - exprstate::on_lambda_token(tk, - p_stack, - p_emit_expr); } void @@ -41,12 +38,9 @@ namespace xo { this->lmxs_type_ = lambdastatetype::lm_2; this->argl_ = argl; p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); - return; + } else { + exprstate::on_formal_arglist(argl, p_stack, p_emit_expr); } - - exprstate::on_formal_arglist(argl, - p_stack, - p_emit_expr); } void @@ -57,10 +51,9 @@ namespace xo { if (lmxs_type_ == lambdastatetype::lm_2) { this->lmxs_type_ = lambdastatetype::lm_3; this->body_ = expr.promote(); - return; + } else { + exprstate::on_expr(expr, p_stack, p_emit_expr); } - - exprstate::on_expr(expr, p_stack, p_emit_expr); } void From de9f813d82dbd83ca1b8e3e526197011a40247a8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 10:19:56 -0400 Subject: [PATCH 1395/2524] xo-reader: utest: + lambda [failing] --- utest/reader.test.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utest/reader.test.cpp b/utest/reader.test.cpp index fb4a170d..926b6436 100644 --- a/utest/reader.test.cpp +++ b/utest/reader.test.cpp @@ -15,7 +15,8 @@ namespace xo { std::vector s_testcase_v = { {"def foo : f64 = 3.14159265;"}, {"def foo : f64 = (3.14159265);"}, - {"def foo : f64 = 2.0 * 3.14159265;"} + //{"def foo : f64 = 2.0 * 3.14159265;"}, + {"def foo = lambda (x : f64) 3.1415965;"} }; } From 8edbfcf21fdfc349c3a2d2a20c28f73f5df9c775 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 11:02:21 -0400 Subject: [PATCH 1396/2524] xo-reader: bugfix: init expect_formal_arglist_xs.farglxs_type --- include/xo/reader/expect_formal_arglist_xs.hpp | 2 +- include/xo/reader/expect_formal_xs.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/xo/reader/expect_formal_arglist_xs.hpp b/include/xo/reader/expect_formal_arglist_xs.hpp index 501d945f..82116133 100644 --- a/include/xo/reader/expect_formal_arglist_xs.hpp +++ b/include/xo/reader/expect_formal_arglist_xs.hpp @@ -71,7 +71,7 @@ namespace xo { private: /** parsing state-machine state **/ - formalarglstatetype farglxs_type_; + formalarglstatetype farglxs_type_ = formalarglstatetype::argl_0; /** populate with (parmaeter-name, parameter-type) list * as they're encountered **/ diff --git a/include/xo/reader/expect_formal_xs.hpp b/include/xo/reader/expect_formal_xs.hpp index b4c046b9..79b8bf05 100644 --- a/include/xo/reader/expect_formal_xs.hpp +++ b/include/xo/reader/expect_formal_xs.hpp @@ -45,7 +45,7 @@ namespace xo { **/ class expect_formal_xs : public exprstate { public: - expect_formal_xs() = default; + expect_formal_xs(); static std::unique_ptr make(); From 2f2a1d2e1373e01c52156ff44023ee9877f4be43 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 11:03:58 -0400 Subject: [PATCH 1397/2524] xo-reader: bugfix: + missing exprstatetype.lambdaexpr --- include/xo/reader/exprstate.hpp | 5 +++++ src/reader/exprstate.cpp | 2 ++ 2 files changed, 7 insertions(+) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 65dd944e..37687a56 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -23,6 +23,11 @@ namespace xo { **/ defexpr, + /** handle lambda-expression + * see @ref lambda_xs + **/ + lambdaexpr, + /** handle parenthesized expression * see @ref paren_xs **/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 859d0be8..a734f3b9 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -27,6 +27,8 @@ namespace xo { return "expect_toplevel_expression_sequence"; case exprstatetype::defexpr: return "defexpr"; + case exprstatetype::lambdaexpr: + return "lambdaexpr"; case exprstatetype::parenexpr: return "parenexpr"; case exprstatetype::expect_rhs_expression: From 67125d5c651fc91cc61162342b73343da2184b30 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 11:04:34 -0400 Subject: [PATCH 1398/2524] xo-reader: bugfix: + missing exprstatetype.expect_formal --- include/xo/reader/exprstate.hpp | 4 ++++ src/reader/exprstate.cpp | 2 ++ 2 files changed, 6 insertions(+) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 37687a56..d5696020 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -36,6 +36,10 @@ namespace xo { expect_rhs_expression, expect_symbol, expect_type, + /** handle formal argument + * see @ref expec_formal_xs + **/ + expect_formal, /** handle expression-in-progress, * in case infix operators to follow diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index a734f3b9..6d3376bc 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -37,6 +37,8 @@ namespace xo { return "expect_symbol"; case exprstatetype::expect_type: return "expect_type"; + case exprstatetype::expect_formal: + return "expect_formal"; case exprstatetype::expr_progress: return "expr_progress"; case exprstatetype::n_exprstatetype: From 8a633a4f94e90590205a13844959b57675fcd2cb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 11:04:59 -0400 Subject: [PATCH 1399/2524] xo-reader: bugfix: + missing exprstatetype.expect_formal_arglist --- include/xo/reader/exprstate.hpp | 4 ++++ src/reader/exprstate.cpp | 2 ++ 2 files changed, 6 insertions(+) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index d5696020..5d3ee107 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -36,6 +36,10 @@ namespace xo { expect_rhs_expression, expect_symbol, expect_type, + /** handle formal argument list + * see @ref expect_formal_arglist_xs + **/ + expect_formal_arglist, /** handle formal argument * see @ref expec_formal_xs **/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 6d3376bc..8352914a 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -37,6 +37,8 @@ namespace xo { return "expect_symbol"; case exprstatetype::expect_type: return "expect_type"; + case exprstatetype::expect_formal_arglist: + return "expect_formal_arglist"; case exprstatetype::expect_formal: return "expect_formal"; case exprstatetype::expr_progress: From e9289e855ec1eec35fb1b347152a488773b1e475 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 11:05:47 -0400 Subject: [PATCH 1400/2524] xo-reader: bugfix: lambda_xs: supply exprstate.exs_type --- include/xo/reader/lambda_xs.hpp | 2 +- src/reader/lambda_xs.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/xo/reader/lambda_xs.hpp b/include/xo/reader/lambda_xs.hpp index 4b4fb74c..1152406a 100644 --- a/include/xo/reader/lambda_xs.hpp +++ b/include/xo/reader/lambda_xs.hpp @@ -57,7 +57,7 @@ namespace xo { private: /** parsing state-machine state **/ - lambdastatetype lmxs_type_; + lambdastatetype lmxs_type_ = lambdastatetype::lm_0; /** formal parameter list **/ std::vector> argl_; diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index a4a19845..d6b1eec1 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -14,7 +14,7 @@ namespace xo { return std::make_unique(lambda_xs()); } - lambda_xs::lambda_xs() {} + lambda_xs::lambda_xs() : exprstate(exprstatetype::lambdaexpr) {} void lambda_xs::on_lambda_token(const token_type & tk, From ebeefdc447f8c187d58268671795ac35a0d4f10b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 11:10:11 -0400 Subject: [PATCH 1401/2524] xo-reader: bugfix: expect_formal_arglist_xs sets exprstate.exs_type --- src/reader/expect_formal_arglist_xs.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index 473cfb0c..0c312302 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -35,7 +35,8 @@ namespace xo { } expect_formal_arglist_xs::expect_formal_arglist_xs() - : farglxs_type_{formalarglstatetype::argl_0} + : exprstate(exprstatetype::expect_formal_arglist), + farglxs_type_{formalarglstatetype::argl_0} {} void From 29638438b5a700bd3a06aa0168677723cd457542 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 11:12:12 -0400 Subject: [PATCH 1402/2524] xo-reader: bugfix: expect_formal_xs supply exprstate.exs_type --- src/reader/expect_formal_xs.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp index 9d69b137..bd940ec6 100644 --- a/src/reader/expect_formal_xs.cpp +++ b/src/reader/expect_formal_xs.cpp @@ -33,6 +33,10 @@ namespace xo { return std::make_unique(expect_formal_xs()); } + expect_formal_xs::expect_formal_xs() + : exprstate(exprstatetype::expect_formal) + {} + void expect_formal_xs::on_symbol(const std::string & symbol_name, exprstatestack * p_stack, From e43d3536c04163189c858849c06217d9dc0e37ea Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 11:12:48 -0400 Subject: [PATCH 1403/2524] xo-reader: bugfix: w/ expect_formal_xs need push expect_symbol_xs --- src/reader/expect_formal_arglist_xs.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index 0c312302..839a2f51 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -5,6 +5,7 @@ #include "expect_formal_arglist_xs.hpp" #include "expect_formal_xs.hpp" +#include "expect_symbol_xs.hpp" #include "xo/expression/Variable.hpp" #include "xo/indentlog/print/vector.hpp" @@ -46,7 +47,9 @@ namespace xo { { if (farglxs_type_ == formalarglstatetype::argl_0) { this->farglxs_type_ = formalarglstatetype::argl_1a; + /* TODO: refactor to have setup method on each exprstate */ p_stack->push_exprstate(expect_formal_xs::make()); + p_stack->push_exprstate(expect_symbol_xs::expect_symbol_expression()); } else { exprstate::on_leftparen_token(tk, p_stack, p_emit_expr); } From 12efbebe2164c9d27d420a0e3b0283264934e447 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 11:13:54 -0400 Subject: [PATCH 1404/2524] xo-reader: bugfix: expect_formal_xs push expect_type_xs for rhs type --- src/reader/expect_formal_xs.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp index bd940ec6..b181583d 100644 --- a/src/reader/expect_formal_xs.cpp +++ b/src/reader/expect_formal_xs.cpp @@ -4,6 +4,7 @@ */ #include "expect_formal_xs.hpp" +#include "expect_type_xs.hpp" #include "xo/expression/Variable.hpp" namespace xo { @@ -59,6 +60,7 @@ namespace xo { { if (this->formalxs_type_ == formalstatetype::formal_1) { this->formalxs_type_ = formalstatetype::formal_2; + p_stack->push_exprstate(expect_type_xs::make()); } else { exprstate::on_colon_token(tk, p_stack); From 818127a44652ea83bdd127c4dea96882e1a77eef Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 23:29:36 -0400 Subject: [PATCH 1405/2524] xo-reader: refactor: simplify define_xs behavior --- include/xo/reader/define_xs.hpp | 61 ++++++---- src/reader/define_xs.cpp | 207 ++++++++++---------------------- src/reader/exprseq_xs.cpp | 7 +- utest/parser.test.cpp | 20 +-- 4 files changed, 112 insertions(+), 183 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 59b55d83..58846f33 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -12,6 +12,36 @@ namespace xo { namespace scm { + /** + * def foo : f64 = 1 ; + * ^ ^ ^ ^ ^ ^ ^ ^ + * | | | | | | | (done) + * | | | | | | def_6:expect_rhs_expression:expr_progress + * | | | | | def_5:expect_rhs_expression + * | | | | def_4 + * | | | def_3:expect_type + * | | def_2 + * | def_1:expect_symbol + * def_0 + * expect_toplevel_expression_sequence + * + * def_0 --on_def_token()--> def_1 + * def_1 --on_symbol()--> def_2 + * def_2 --on_colon_token()--> def_3 + * --on_singleassign_token()--> def_5 + * def_3 --on_typedescr()--> def_4 + * def_4 --on_singleassign_token()--> def_5 + * def_5 --on_expr()--> def_6 + * def_6 --on_semicolon_token()--> (done) + * + * def_1:expect_symbol: got 'def' keyword, symbol to follow + * def_1: got symbol name + * def_3:expect_symbol got (optional) colon, type name to follow + * def_4: got symbol type + * def_6:expect_rhs_expression got (optional) equal sign, value to follow + * (done): definition complete, pop exprstate from stack + * + **/ enum class defexprstatetype { invalid = -1, @@ -21,6 +51,7 @@ namespace xo { def_3, def_4, def_5, + def_6, n_defexprstatetype, }; @@ -38,12 +69,13 @@ namespace xo { virtual ~define_xs() = default; static const define_xs * from(const exprstate * x) { return dynamic_cast(x); } - static std::unique_ptr def_0(); + + static void start(exprstatestack * p_stack); defexprstatetype defxs_type() const { return defxs_type_; } bool admits_rightparen() const; - bool admits_colon() const; + //bool admits_colon() const; bool admits_semicolon() const; //bool admits_symbol() const; @@ -58,6 +90,8 @@ namespace xo { virtual void on_typedescr(TypeDescr td, exprstatestack * p_stack, rp * p_emit_expr) override; + virtual void on_def_token(const token_type & tk, + exprstatestack * p_stack) override; virtual void on_colon_token(const token_type & tk, exprstatestack * p_stack) override; virtual void on_semicolon_token(const token_type & tk, @@ -75,26 +109,9 @@ namespace xo { virtual void print(std::ostream & os) const override; private: - /** - * def foo : f64 = 1 ; - * ^ ^ ^ ^ ^ ^ ^ ^ - * | | | | | | | (done) - * | | | | | | def_4:expect_rhs_expression:expr_progress - * | | | | | def_4:expect_rhs_expression - * | | | | def_3 - * | | | def_2:expect_type - * | | def_1 - * | def_0:expect_symbol - * expect_toplevel_expression_sequence - * - * def_0:expect_symbol: got 'def' keyword, symbol to follow - * def_1: got symbol name - * def_2:expect_symbol got (optional) colon, type name to follow - * def_3: got symbol type - * def_4:expect_rhs_expression got (optional) equal sign, value to follow - * (done): definition complete, pop exprstate from stack - * - **/ + static std::unique_ptr make(); + + private: defexprstatetype defxs_type_; /** scaffold a define-expression here **/ rp def_expr_; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index d3c2d5c2..c3b977a2 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -1,48 +1,30 @@ /* @file define_xs.cpp */ #include "define_xs.hpp" +#include "expect_symbol_xs.hpp" #include "expect_expr_xs.hpp" #include "expect_type_xs.hpp" namespace xo { namespace scm { std::unique_ptr - define_xs::def_0() { + define_xs::make() { return std::make_unique(define_xs(DefineExprAccess::make_empty())); } + void + define_xs::start(exprstatestack * p_stack) + { + p_stack->push_exprstate(define_xs::make()); + p_stack->top_exprstate().on_def_token(token_type::def(), p_stack); + } + define_xs::define_xs(rp def_expr) : exprstate(exprstatetype::defexpr), defxs_type_{defexprstatetype::def_0}, def_expr_{std::move(def_expr)} {} - bool - define_xs::admits_colon() const { - switch (defxs_type_) { - - case defexprstatetype::def_0: - return false; - - case defexprstatetype::def_1: - return true; - - case defexprstatetype::def_2: - case defexprstatetype::def_3: - case defexprstatetype::def_4: - case defexprstatetype::def_5: - return false; - - case defexprstatetype::invalid: - case defexprstatetype::n_defexprstatetype: - /* unreachable */ - assert(false); - return false; - } - - return false; - } - bool define_xs::admits_semicolon() const { switch (defxs_type_) { @@ -52,8 +34,9 @@ namespace xo { case defexprstatetype::def_2: case defexprstatetype::def_3: case defexprstatetype::def_4: - return false; case defexprstatetype::def_5: + return false; + case defexprstatetype::def_6: return true; case defexprstatetype::invalid: @@ -66,73 +49,6 @@ namespace xo { return false; } -#ifdef OBSOLETE - bool - define_xs::admits_singleassign() const { - switch (defxs_type_) { - - case defexprstatetype::def_0: - return false; - - case defexprstatetype::def_1: - return true; - - case defexprstatetype::def_2: - return false; - - case defexprstatetype::def_3: - return true; - - case defexprstatetype::def_4: - case defexprstatetype::def_5: - return false; - - case defexprstatetype::invalid: - case defexprstatetype::n_defexprstatetype: - /* unreachable */ - assert(false); - return false; - } - - return false; - } -#endif - -#ifdef OBSOLETE - bool - define_xs::admits_leftparen() const { - switch (defxs_type_) { - - case defexprstatetype::def_0: - case defexprstatetype::def_1: - case defexprstatetype::def_2: - case defexprstatetype::def_3: - case defexprstatetype::def_4: - case defexprstatetype::def_5: - /* input like - * def foo : f64 = ( - * ^ ^ ^ ^ ^ - * | | | | def_4 - * | | | def_3 - * | | def_2 - * | def_1 - * def_0 - * - * not allowed or relies on pushing another state - */ - return false; - - case defexprstatetype::invalid: - case defexprstatetype::n_defexprstatetype: - /* unreachable */ - assert(false); - return false; - } - - return false; - } -#endif - bool define_xs::admits_rightparen() const { switch (defxs_type_) { @@ -143,6 +59,7 @@ namespace xo { case defexprstatetype::def_3: case defexprstatetype::def_4: case defexprstatetype::def_5: + case defexprstatetype::def_6: return false; case defexprstatetype::invalid: @@ -166,10 +83,11 @@ namespace xo { case defexprstatetype::def_1: case defexprstatetype::def_2: case defexprstatetype::def_3: + case defexprstatetype::def_4: /* NOT IMPLEMENTED */ assert(false); return; - case defexprstatetype::def_4: { + case defexprstatetype::def_5: { /* have all the ingredients to create an expression * representing a definition * @@ -186,11 +104,11 @@ namespace xo { rp def_expr = this->def_expr_; - this->defxs_type_ = defexprstatetype::def_5; + this->defxs_type_ = defexprstatetype::def_6; return; } - case defexprstatetype::def_5: + case defexprstatetype::def_6: assert(false); return; @@ -209,16 +127,20 @@ namespace xo { { switch (this->defxs_type_) { case defexprstatetype::def_0: - this->defxs_type_ = defexprstatetype::def_1; + assert(false); + return; + + case defexprstatetype::def_1: + this->defxs_type_ = defexprstatetype::def_2; this->def_expr_->assign_lhs_name(symbol_name); //this->def_lhs_symbol_ = symbol_name; return; - case defexprstatetype::def_1: case defexprstatetype::def_2: case defexprstatetype::def_3: case defexprstatetype::def_4: case defexprstatetype::def_5: + case defexprstatetype::def_6: /* NOT IMPLEMENTED */ assert(false); return; @@ -240,12 +162,13 @@ namespace xo { case defexprstatetype::def_0: case defexprstatetype::def_1: + case defexprstatetype::def_2: /* NOT IMPLEMENTED (ill-formed program) */ assert(false); return; - case defexprstatetype::def_2: - this->defxs_type_ = defexprstatetype::def_3; + case defexprstatetype::def_3: + this->defxs_type_ = defexprstatetype::def_4; this->cvt_expr_ = ConvertExprAccess::make(td /*dest_type*/, nullptr /*source_expr*/); this->def_expr_->assign_rhs(this->cvt_expr_); @@ -253,9 +176,9 @@ namespace xo { return; - case defexprstatetype::def_3: case defexprstatetype::def_4: case defexprstatetype::def_5: + case defexprstatetype::def_6: /* NOT IMPLEMENTED */ assert(false); return; @@ -269,28 +192,38 @@ namespace xo { } void - define_xs::on_colon_token(const token_type & /*tk*/, + define_xs::on_def_token(const token_type & tk, + exprstatestack * p_stack) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + //constexpr const char * self_name = "define_xs::on_def_token"; + + if (this->defxs_type_ == defexprstatetype::def_0) { + this->defxs_type_ = defexprstatetype::def_1; + + p_stack->push_exprstate(expect_symbol_xs::expect_symbol_expression()); + } else { + exprstate::on_def_token(tk, p_stack); + } + } + + void + define_xs::on_colon_token(const token_type & tk, exprstatestack * p_stack) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - constexpr const char * self_name = "exprstate::on_colon"; + //constexpr const char * self_name = "define_xs::on_colon_token"; - /* lots of illegal states */ - if (!this->admits_colon()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected colon for parsing state", - xtag("state", *this))); - } - - if (this->defxs_type_ == defexprstatetype::def_1) { - this->defxs_type_ = defexprstatetype::def_2; + if (this->defxs_type_ == defexprstatetype::def_2) { + this->defxs_type_ = defexprstatetype::def_3; p_stack->push_exprstate(expect_type_xs::make()); } else { - assert(false); + exprstate::on_colon_token(tk, p_stack); } } @@ -311,7 +244,7 @@ namespace xo { xtag("state", *this))); } - if (this->defxs_type_ == defexprstatetype::def_5) { + if (this->defxs_type_ == defexprstatetype::def_6) { rp expr = this->def_expr_; std::unique_ptr self = p_stack->pop_exprstate(); @@ -336,21 +269,22 @@ namespace xo { /* * def foo = 1 ; * def foo : f64 = 1 ; - * ^ ^ ^ ^ ^ ^ ^ - * | | | | | | (done) - * | | | | | def_4:expect_rhs_expression - * | | | | def_3 - * | | | def_2:expect_type - * | | def_1 - * | def_0:expect_symbol + * ^ ^ ^ ^ ^ ^ ^ ^ + * | | | | | | | (done) + * | | | | | | def_6 + * | | | | | def_5:expect_rhs_expression + * | | | | def_4 + * | | | def_3:expect_type + * | | def_2 + * | def_1:expect_symbol * expect_toplevel_expression_sequence * - * note that we skip from def_1 -> def_4 if '=' instead of ':' + * note that we skip from def_2 -> def_5 if '=' instead of ':' */ - if ((this->defxs_type_ == defexprstatetype::def_1) - || (this->defxs_type_ == defexprstatetype::def_3)) + if ((this->defxs_type_ == defexprstatetype::def_2) + || (this->defxs_type_ == defexprstatetype::def_4)) { - this->defxs_type_ = defexprstatetype::def_4; + this->defxs_type_ = defexprstatetype::def_5; p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); } else { @@ -358,23 +292,6 @@ namespace xo { } } -#ifdef OBSOLETE - void - define_xs::on_leftparen_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) - { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag)); - - constexpr const char * self_name = "exprstate::on_leftparen"; - - this->illegal_input_error(self_name, tk); - - assert(false); /* inserting this during refactor...? */ - } -#endif - void define_xs::on_rightparen_token(const token_type & tk, exprstatestack * /*p_stack*/, diff --git a/src/reader/exprseq_xs.cpp b/src/reader/exprseq_xs.cpp index 042c5eb3..ed613dd2 100644 --- a/src/reader/exprseq_xs.cpp +++ b/src/reader/exprseq_xs.cpp @@ -26,12 +26,7 @@ namespace xo { //constexpr const char * c_self_name = "exprseq_xs::on_def_token"; - p_stack->push_exprstate(define_xs::def_0()); - - /* todo: replace: - * expect_symbol_or_function_signature() - */ - p_stack->push_exprstate(expect_symbol_xs::expect_symbol_expression()); + define_xs::start(p_stack); /* keyword 'def' introduces a definition: * def pi : f64 = 3.14159265 diff --git a/utest/parser.test.cpp b/utest/parser.test.cpp index 8611a40a..e2190f91 100644 --- a/utest/parser.test.cpp +++ b/utest/parser.test.cpp @@ -42,7 +42,7 @@ namespace xo { /* stack should be: * * expect_toplevel_expression_sequence - * def_0 + * def_1 * expect_symbol */ CHECK(parser.stack_size() == 3); @@ -51,7 +51,7 @@ namespace xo { if (parser.stack_size() > 1) { CHECK(parser.i_exstype(1) == exprstatetype::defexpr); REQUIRE(define_xs::from(parser.i_exstate(1)) != nullptr); - CHECK(define_xs::from(parser.i_exstate(1))->defxs_type() == defexprstatetype::def_0); + CHECK(define_xs::from(parser.i_exstate(1))->defxs_type() == defexprstatetype::def_1); } if (parser.stack_size() > 2) CHECK(parser.i_exstype(2) @@ -80,7 +80,7 @@ namespace xo { if (parser.stack_size() > 0) { CHECK(parser.i_exstype(0) == exprstatetype::defexpr); REQUIRE(define_xs::from(parser.i_exstate(0)) != nullptr); - CHECK(define_xs::from(parser.i_exstate(0))->defxs_type() == defexprstatetype::def_1); + CHECK(define_xs::from(parser.i_exstate(0))->defxs_type() == defexprstatetype::def_2); } if (parser.stack_size() > 1) CHECK(parser.i_exstype(1) @@ -107,7 +107,7 @@ namespace xo { /* stack should be: * * expect_toplevel_expression_sequence - * def_2 + * def_3 * expect_symbol */ CHECK(parser.stack_size() == 3); @@ -116,7 +116,7 @@ namespace xo { if (parser.stack_size() > 1) { CHECK(parser.i_exstype(1) == exprstatetype::defexpr); REQUIRE(define_xs::from(parser.i_exstate(1)) != nullptr); - CHECK(define_xs::from(parser.i_exstate(1))->defxs_type() == defexprstatetype::def_2); + CHECK(define_xs::from(parser.i_exstate(1))->defxs_type() == defexprstatetype::def_3); } if (parser.stack_size() > 2) CHECK(parser.i_exstype(2) @@ -141,13 +141,13 @@ namespace xo { /* stack should be: * * expect_toplevel_expression_sequence - * def_3 + * def_4 */ CHECK(parser.stack_size() == 2); if (parser.stack_size() > 0) { CHECK(parser.i_exstype(0) == exprstatetype::defexpr); REQUIRE(define_xs::from(parser.i_exstate(0)) != nullptr); - CHECK(define_xs::from(parser.i_exstate(0))->defxs_type() == defexprstatetype::def_3); + CHECK(define_xs::from(parser.i_exstate(0))->defxs_type() == defexprstatetype::def_4); } if (parser.stack_size() > 1) CHECK(parser.i_exstype(1) @@ -185,7 +185,7 @@ namespace xo { /* stack should be * * expect_toplevel_expression_sequence - * def_4 + * def_5 * expect_expression */ CHECK(parser.stack_size() == 3); @@ -194,7 +194,7 @@ namespace xo { if (parser.stack_size() > 1) { CHECK(parser.i_exstype(1) == exprstatetype::defexpr); REQUIRE(define_xs::from(parser.i_exstate(1)) != nullptr); - CHECK(define_xs::from(parser.i_exstate(1))->defxs_type() == defexprstatetype::def_4); + CHECK(define_xs::from(parser.i_exstate(1))->defxs_type() == defexprstatetype::def_5); } if (parser.stack_size() > 2) CHECK(parser.i_exstype(2) @@ -233,7 +233,7 @@ namespace xo { if (parser.stack_size() > 2) { CHECK(parser.i_exstype(2) == exprstatetype::defexpr); REQUIRE(define_xs::from(parser.i_exstate(2)) != nullptr); - CHECK(define_xs::from(parser.i_exstate(2))->defxs_type() == defexprstatetype::def_4); + CHECK(define_xs::from(parser.i_exstate(2))->defxs_type() == defexprstatetype::def_5); } if (parser.stack_size() > 3) CHECK(parser.i_exstype(3) From 5b221b1fae94fa6c44203cf6950cdbb78587e3cd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 23:31:45 -0400 Subject: [PATCH 1406/2524] xo-reader: refactor: streamline define_xs --- include/xo/reader/define_xs.hpp | 4 +--- src/reader/define_xs.cpp | 37 +++------------------------------ 2 files changed, 4 insertions(+), 37 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 58846f33..5d7a0639 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -75,9 +75,7 @@ namespace xo { defexprstatetype defxs_type() const { return defxs_type_; } bool admits_rightparen() const; - //bool admits_colon() const; - bool admits_semicolon() const; - + //bool admits_semicolon() const; //bool admits_symbol() const; // virtual void on_f64(..) override diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index c3b977a2..9a6fe858 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -25,30 +25,6 @@ namespace xo { def_expr_{std::move(def_expr)} {} - bool - define_xs::admits_semicolon() const { - switch (defxs_type_) { - - case defexprstatetype::def_0: - case defexprstatetype::def_1: - case defexprstatetype::def_2: - case defexprstatetype::def_3: - case defexprstatetype::def_4: - case defexprstatetype::def_5: - return false; - case defexprstatetype::def_6: - return true; - - case defexprstatetype::invalid: - case defexprstatetype::n_defexprstatetype: - /* unreachable */ - assert(false); - return false; - } - - return false; - } - bool define_xs::admits_rightparen() const { switch (defxs_type_) { @@ -228,21 +204,14 @@ namespace xo { } void - define_xs::on_semicolon_token(const token_type & /*tk*/, + define_xs::on_semicolon_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - constexpr const char * self_name = "exprstate::on_semicolon"; - - if (!this->admits_semicolon()) - { - throw std::runtime_error(tostr(self_name, - ": unexpected semicolon for parsing state", - xtag("state", *this))); - } + //constexpr const char * self_name = "exprstate::on_semicolon"; if (this->defxs_type_ == defexprstatetype::def_6) { rp expr = this->def_expr_; @@ -253,7 +222,7 @@ namespace xo { p_stack, p_emit_expr); } else { - assert(false); + exprstate::on_semicolon_token(tk, p_stack, p_emit_expr); } } From b607c8b6dcca98ef78ee8cadda89151544e1b48e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 23:33:32 -0400 Subject: [PATCH 1407/2524] xo-reader: refactor: streamline define_xs --- include/xo/reader/define_xs.hpp | 5 ----- src/reader/define_xs.cpp | 30 +----------------------------- 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 5d7a0639..67c53878 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -74,11 +74,6 @@ namespace xo { defexprstatetype defxs_type() const { return defxs_type_; } - bool admits_rightparen() const; - //bool admits_semicolon() const; - //bool admits_symbol() const; - - // virtual void on_f64(..) override virtual void on_expr(ref::brw expr, exprstatestack * p_stack, rp * p_emit_expr) override; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 9a6fe858..6204ef3d 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -25,29 +25,6 @@ namespace xo { def_expr_{std::move(def_expr)} {} - bool - define_xs::admits_rightparen() const { - switch (defxs_type_) { - - case defexprstatetype::def_0: - case defexprstatetype::def_1: - case defexprstatetype::def_2: - case defexprstatetype::def_3: - case defexprstatetype::def_4: - case defexprstatetype::def_5: - case defexprstatetype::def_6: - return false; - - case defexprstatetype::invalid: - case defexprstatetype::n_defexprstatetype: - /* unreachable */ - assert(false); - return false; - } - - return false; - } - void define_xs::on_expr(ref::brw expr, exprstatestack * /* p_stack */, @@ -271,12 +248,7 @@ namespace xo { constexpr const char * self_name = "exprstate::on_rightparen"; - if (!this->admits_rightparen()) - { - this->illegal_input_error(self_name, tk); - } - - assert(false); /* inserting this during refactor..? */ + this->illegal_input_error(self_name, tk); } void From e8e03f7b4cf5cb14d796b5e9c6579060c8efb60c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 23:36:56 -0400 Subject: [PATCH 1408/2524] xo-reader: refactor: streamline define_xs impl --- src/reader/define_xs.cpp | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 6204ef3d..4795fe5f 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -27,20 +27,10 @@ namespace xo { void define_xs::on_expr(ref::brw expr, - exprstatestack * /* p_stack */, - rp * /* p_emit_expr */) + exprstatestack * p_stack, + rp * p_emit_expr) { - switch (this->defxs_type_) { - - case defexprstatetype::def_0: - case defexprstatetype::def_1: - case defexprstatetype::def_2: - case defexprstatetype::def_3: - case defexprstatetype::def_4: - /* NOT IMPLEMENTED */ - assert(false); - return; - case defexprstatetype::def_5: { + if (this->defxs_type_ == defexprstatetype::def_5) { /* have all the ingredients to create an expression * representing a definition * @@ -61,16 +51,7 @@ namespace xo { return; } - case defexprstatetype::def_6: - assert(false); - return; - - case defexprstatetype::invalid: - case defexprstatetype::n_defexprstatetype: - /* unreachable */ - assert(false); - return; - } + exprstate::on_expr(expr, p_stack, p_emit_expr); } void From f432e950abcb0ecb498cae2d6bb7a6e1dcf17f7d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 23:38:53 -0400 Subject: [PATCH 1409/2524] xo-reader: refactor: streamline define_xs impl --- src/reader/define_xs.cpp | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 4795fe5f..d983bdf2 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -56,34 +56,15 @@ namespace xo { void define_xs::on_symbol(const std::string & symbol_name, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + exprstatestack * p_stack, + rp * p_emit_expr) { - switch (this->defxs_type_) { - case defexprstatetype::def_0: - assert(false); - return; - - case defexprstatetype::def_1: + if (this->defxs_type_ == defexprstatetype::def_1) { this->defxs_type_ = defexprstatetype::def_2; this->def_expr_->assign_lhs_name(symbol_name); - //this->def_lhs_symbol_ = symbol_name; - - return; - case defexprstatetype::def_2: - case defexprstatetype::def_3: - case defexprstatetype::def_4: - case defexprstatetype::def_5: - case defexprstatetype::def_6: - /* NOT IMPLEMENTED */ - assert(false); - return; - - case defexprstatetype::invalid: - case defexprstatetype::n_defexprstatetype: - /* unreachable */ - assert(false); return; + } else { + exprstate::on_symbol(symbol_name, p_stack, p_emit_expr); } } From 8bff8adc57d9d388353fddb81cd676b4044d049d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 23:40:43 -0400 Subject: [PATCH 1410/2524] xo-reader: refactor: streamline define_xs impl --- src/reader/define_xs.cpp | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index d983bdf2..b813dc4d 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -70,19 +70,10 @@ namespace xo { void define_xs::on_typedescr(TypeDescr td, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + exprstatestack * p_stack, + rp * p_emit_expr) { - switch (this->defxs_type_) { - - case defexprstatetype::def_0: - case defexprstatetype::def_1: - case defexprstatetype::def_2: - /* NOT IMPLEMENTED (ill-formed program) */ - assert(false); - return; - - case defexprstatetype::def_3: + if (this->defxs_type_ == defexprstatetype::def_3) { this->defxs_type_ = defexprstatetype::def_4; this->cvt_expr_ = ConvertExprAccess::make(td /*dest_type*/, nullptr /*source_expr*/); @@ -90,19 +81,8 @@ namespace xo { //this->def_lhs_td_ = td; return; - - case defexprstatetype::def_4: - case defexprstatetype::def_5: - case defexprstatetype::def_6: - /* NOT IMPLEMENTED */ - assert(false); - return; - - case defexprstatetype::invalid: - case defexprstatetype::n_defexprstatetype: - /* unreachable */ - assert(false); - return; + } else { + exprstate::on_typedescr(td, p_stack, p_emit_expr); } } From f8754913bb31827083a3696a267a7c59f3fa35a5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 23:43:59 -0400 Subject: [PATCH 1411/2524] xo-reader: refactor: simplify expect_symbol_xs api --- include/xo/reader/expect_symbol_xs.hpp | 4 +++- src/reader/define_xs.cpp | 2 +- src/reader/expect_formal_arglist_xs.cpp | 2 +- src/reader/expect_symbol_xs.cpp | 8 +++++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/include/xo/reader/expect_symbol_xs.hpp b/include/xo/reader/expect_symbol_xs.hpp index 144bc3d0..40cb25b6 100644 --- a/include/xo/reader/expect_symbol_xs.hpp +++ b/include/xo/reader/expect_symbol_xs.hpp @@ -18,7 +18,9 @@ namespace xo { public: expect_symbol_xs(); - static std::unique_ptr expect_symbol_expression(); + static std::unique_ptr make(); + + static void start(exprstatestack * p_stack); virtual void on_symbol_token(const token_type & tk, exprstatestack * p_stack, diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index b813dc4d..6eab1bb7 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -98,7 +98,7 @@ namespace xo { if (this->defxs_type_ == defexprstatetype::def_0) { this->defxs_type_ = defexprstatetype::def_1; - p_stack->push_exprstate(expect_symbol_xs::expect_symbol_expression()); + expect_symbol_xs::start(p_stack); } else { exprstate::on_def_token(tk, p_stack); } diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index 839a2f51..2c67c474 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -49,7 +49,7 @@ namespace xo { this->farglxs_type_ = formalarglstatetype::argl_1a; /* TODO: refactor to have setup method on each exprstate */ p_stack->push_exprstate(expect_formal_xs::make()); - p_stack->push_exprstate(expect_symbol_xs::expect_symbol_expression()); + expect_symbol_xs::start(p_stack); } else { exprstate::on_leftparen_token(tk, p_stack, p_emit_expr); } diff --git a/src/reader/expect_symbol_xs.cpp b/src/reader/expect_symbol_xs.cpp index 84faeaa3..e6fd2bb7 100644 --- a/src/reader/expect_symbol_xs.cpp +++ b/src/reader/expect_symbol_xs.cpp @@ -8,10 +8,16 @@ namespace xo { namespace scm { std::unique_ptr - expect_symbol_xs::expect_symbol_expression() { + expect_symbol_xs::make() { return std::make_unique(expect_symbol_xs()); } + void + expect_symbol_xs::start(exprstatestack * p_stack) + { + p_stack->push_exprstate(expect_symbol_xs::make()); + } + expect_symbol_xs::expect_symbol_xs() : exprstate(exprstatetype::expect_symbol) {} From 2b6b15480ea2dcc398b5e2c2be8a2e98391cc459 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 23:47:47 -0400 Subject: [PATCH 1412/2524] expect_type_xs: refactor: simplify api --- include/xo/reader/expect_type_xs.hpp | 5 ++++- src/reader/define_xs.cpp | 2 +- src/reader/expect_formal_xs.cpp | 3 ++- src/reader/expect_type_xs.cpp | 5 +++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/include/xo/reader/expect_type_xs.hpp b/include/xo/reader/expect_type_xs.hpp index c75c91cb..0e534a2a 100644 --- a/include/xo/reader/expect_type_xs.hpp +++ b/include/xo/reader/expect_type_xs.hpp @@ -16,11 +16,14 @@ namespace xo { public: expect_type_xs(); - static std::unique_ptr make(); + static void start(exprstatestack * p_stack); virtual void on_symbol_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) override; + + private: + static std::unique_ptr make(); }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 6eab1bb7..96514b97 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -116,7 +116,7 @@ namespace xo { if (this->defxs_type_ == defexprstatetype::def_2) { this->defxs_type_ = defexprstatetype::def_3; - p_stack->push_exprstate(expect_type_xs::make()); + expect_type_xs::start(p_stack); } else { exprstate::on_colon_token(tk, p_stack); } diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp index b181583d..b796a209 100644 --- a/src/reader/expect_formal_xs.cpp +++ b/src/reader/expect_formal_xs.cpp @@ -60,7 +60,8 @@ namespace xo { { if (this->formalxs_type_ == formalstatetype::formal_1) { this->formalxs_type_ = formalstatetype::formal_2; - p_stack->push_exprstate(expect_type_xs::make()); + expect_type_xs::start(p_stack); + /* control reenters via expect_formal_xs::on_typedescr() */ } else { exprstate::on_colon_token(tk, p_stack); diff --git a/src/reader/expect_type_xs.cpp b/src/reader/expect_type_xs.cpp index fa4a517a..add0dc77 100644 --- a/src/reader/expect_type_xs.cpp +++ b/src/reader/expect_type_xs.cpp @@ -17,6 +17,11 @@ namespace xo { return std::make_unique(expect_type_xs()); } + void + expect_type_xs::start(exprstatestack * p_stack) { + p_stack->push_exprstate(expect_type_xs::make()); + } + expect_type_xs::expect_type_xs() : exprstate(exprstatetype::expect_type) {} From 9a42f02f0c119d6c98e70f0c5c4cbeaaa52e5a54 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 23:53:06 -0400 Subject: [PATCH 1413/2524] xo-reader: refactor: simplify expect_expr_xs api --- include/xo/reader/expect_expr_xs.hpp | 3 ++- src/reader/define_xs.cpp | 2 +- src/reader/expect_expr_xs.cpp | 9 +++++++-- src/reader/lambda_xs.cpp | 2 +- src/reader/progress_xs.cpp | 8 ++++---- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/include/xo/reader/expect_expr_xs.hpp b/include/xo/reader/expect_expr_xs.hpp index 740eecc8..85686d96 100644 --- a/include/xo/reader/expect_expr_xs.hpp +++ b/include/xo/reader/expect_expr_xs.hpp @@ -17,7 +17,7 @@ namespace xo { public: expect_expr_xs(); - static std::unique_ptr expect_rhs_expression(); + static void start(exprstatestack * p_stack); virtual void on_lambda_token(const token_type & tk, exprstatestack * p_stack, @@ -40,6 +40,7 @@ namespace xo { rp * p_emit_expr) override; private: + static std::unique_ptr make(); }; } /*namespace scm*/ diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 96514b97..20ffbd40 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -174,7 +174,7 @@ namespace xo { { this->defxs_type_ = defexprstatetype::def_5; - p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + expect_expr_xs::start(p_stack); } else { this->illegal_input_error(self_name, tk); } diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index fe4a1830..edc67357 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -16,11 +16,16 @@ namespace xo { namespace scm { std::unique_ptr - expect_expr_xs::expect_rhs_expression() { + expect_expr_xs::make() { return std::make_unique(expect_expr_xs()); } + void + expect_expr_xs::start(exprstatestack * p_stack) { + p_stack->push_exprstate(expect_expr_xs::make()); + } + expect_expr_xs::expect_expr_xs() : exprstate(exprstatetype::expect_rhs_expression) {} @@ -53,7 +58,7 @@ namespace xo { /* push lparen_0 to remember to look for subsequent rightparen. */ p_stack->push_exprstate(paren_xs::lparen_0()); - p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + expect_expr_xs::start(p_stack); } void diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index d6b1eec1..82b91ae2 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -37,7 +37,7 @@ namespace xo { if (lmxs_type_ == lambdastatetype::lm_1) { this->lmxs_type_ = lambdastatetype::lm_2; this->argl_ = argl; - p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + expect_expr_xs::start(p_stack); } else { exprstate::on_formal_arglist(argl, p_stack, p_emit_expr); } diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 421524a2..93862147 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -312,7 +312,7 @@ namespace xo { this->op_type_ = tk2op(tk.tk_type()); /* infix operator must be followed by non-empty expression */ - p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + expect_expr_xs::start(p_stack); } else if (rhs_) { /* already have complete expression stashed. * behavior depends on operator precedence for tk with stored operator @@ -341,7 +341,7 @@ namespace xo { p_stack->push_exprstate(progress_xs::make(expr, op2)); /* infix operator must be followed by non-empty expression */ - p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + expect_expr_xs::start(p_stack); } else { /* e.g. * 6.2 + 4.9 * ... @@ -360,9 +360,9 @@ namespace xo { /* 1. replace with nested incomplete infix exprs */ p_stack->push_exprstate(progress_xs::make(lhs_, op_type_)); - p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + expect_expr_xs::start(p_stack); p_stack->push_exprstate(progress_xs::make(rhs_, op2)); - p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + expect_expr_xs::start(p_stack); } } else { From bd8f093a00f292675730c2118e8dcb1e4ba73a16 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 23:56:50 -0400 Subject: [PATCH 1414/2524] xo-reader: refactor: simplify exprseq_xs api --- include/xo/reader/exprseq_xs.hpp | 4 +++- src/reader/exprseq_xs.cpp | 8 +++++++- src/reader/parser.cpp | 3 +-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/include/xo/reader/exprseq_xs.hpp b/include/xo/reader/exprseq_xs.hpp index d6afffe8..1797855a 100644 --- a/include/xo/reader/exprseq_xs.hpp +++ b/include/xo/reader/exprseq_xs.hpp @@ -18,7 +18,7 @@ namespace xo { public: exprseq_xs(); - static std::unique_ptr expect_toplevel_expression_sequence(); + static void start(exprstatestack * p_stack); public: // ----- token input methods ----- @@ -38,6 +38,8 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr) override; + private: + static std::unique_ptr make(); }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprseq_xs.cpp b/src/reader/exprseq_xs.cpp index ed613dd2..840cd958 100644 --- a/src/reader/exprseq_xs.cpp +++ b/src/reader/exprseq_xs.cpp @@ -7,11 +7,17 @@ namespace xo { namespace scm { std::unique_ptr - exprseq_xs::expect_toplevel_expression_sequence() + exprseq_xs::make() { return std::make_unique(exprseq_xs()); } + void + exprseq_xs::start(exprstatestack * p_stack) + { + p_stack->push_exprstate(exprseq_xs::make()); + } + exprseq_xs::exprseq_xs() : exprstate(exprstatetype::expect_toplevel_expression_sequence) { diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index 5ac71dd0..5c2a99bb 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -30,8 +30,7 @@ namespace xo { void parser::begin_translation_unit() { - xs_stack_.push_exprstate - (exprseq_xs::expect_toplevel_expression_sequence()); + exprseq_xs::start(&xs_stack_); } rp From 5916ac874ff14ceae950cb863dc2c0fdaf0410f2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 18 Aug 2024 23:59:01 -0400 Subject: [PATCH 1415/2524] xo-reader: refactor: simplify expect_formal_arglist_xs api --- include/xo/reader/expect_formal_arglist_xs.hpp | 5 ++++- src/reader/expect_formal_arglist_xs.cpp | 6 ++++++ src/reader/lambda_xs.cpp | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/include/xo/reader/expect_formal_arglist_xs.hpp b/include/xo/reader/expect_formal_arglist_xs.hpp index 82116133..442dde62 100644 --- a/include/xo/reader/expect_formal_arglist_xs.hpp +++ b/include/xo/reader/expect_formal_arglist_xs.hpp @@ -53,7 +53,7 @@ namespace xo { public: expect_formal_arglist_xs(); - static std::unique_ptr make(); + static void start(exprstatestack * p_stack); virtual void on_leftparen_token(const token_type & tk, exprstatestack * p_stack, @@ -69,6 +69,9 @@ namespace xo { rp * p_emit_expr) override; virtual void print(std::ostream & os) const override; + private: + static std::unique_ptr make(); + private: /** parsing state-machine state **/ formalarglstatetype farglxs_type_ = formalarglstatetype::argl_0; diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index 2c67c474..6f2a4e13 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -35,6 +35,12 @@ namespace xo { (expect_formal_arglist_xs()); } + void + expect_formal_arglist_xs::start(exprstatestack * p_stack) + { + p_stack->push_exprstate(expect_formal_arglist_xs::make()); + } + expect_formal_arglist_xs::expect_formal_arglist_xs() : exprstate(exprstatetype::expect_formal_arglist), farglxs_type_{formalarglstatetype::argl_0} diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index 82b91ae2..c9c131ac 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -23,7 +23,7 @@ namespace xo { { if (lmxs_type_ == lambdastatetype::lm_0) { this->lmxs_type_ = lambdastatetype::lm_1; - p_stack->push_exprstate(expect_formal_arglist_xs::make()); + expect_formal_arglist_xs::start(p_stack); } else { exprstate::on_lambda_token(tk, p_stack, p_emit_expr); } From 6d73caf308f1b6670af86bee70b45703027c55cc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 00:06:34 -0400 Subject: [PATCH 1416/2524] xo-reader: refactor: simplify progress_xs api --- include/xo/reader/progress_xs.hpp | 11 +++++++++-- src/reader/expect_expr_xs.cpp | 6 +++--- src/reader/paren_xs.cpp | 2 +- src/reader/progress_xs.cpp | 10 ++++++++++ 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index eeaee588..466b4b02 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -47,8 +47,11 @@ namespace xo { return dynamic_cast(x); } - static std::unique_ptr make(rp valex, - optype optype = optype::invalid); + static void start(rp valex, + optype optype, + exprstatestack * p_stack); + static void start(rp valex, + exprstatestack * p_stack); bool admits_f64() const; @@ -89,6 +92,10 @@ namespace xo { virtual void print(std::ostream & os) const override; + private: + static std::unique_ptr make(rp valex, + optype optype = optype::invalid); + private: /** assemble expression representing * value of diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index edc67357..c91205d0 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -110,9 +110,9 @@ namespace xo { * def pi = 3.14159265; * \---tk---/ */ - p_stack->push_exprstate - (progress_xs::make - (Constant::make(tk.f64_value()))); + progress_xs::start + (Constant::make(tk.f64_value()), + p_stack); } void diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index cfd20f95..06329bde 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -183,7 +183,7 @@ namespace xo { switch (this->parenxs_type_) { case parenexprstatetype::lparen_0: { this->parenxs_type_ = parenexprstatetype::lparen_1; /* wants on_rightparen */ - p_stack->push_exprstate(progress_xs::make(expr.promote())); + progress_xs::start(expr.promote(), p_stack); return; } diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 93862147..61d1cd77 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -52,6 +52,16 @@ namespace xo { return std::make_unique(progress_xs(std::move(valex), op)); } + void + progress_xs::start(rp valex, optype op, exprstatestack * p_stack) { + p_stack->push_exprstate(progress_xs::make(valex, op)); + } + + void + progress_xs::start(rp valex, exprstatestack * p_stack) { + p_stack->push_exprstate(progress_xs::make(valex)); + } + progress_xs::progress_xs(rp valex, optype op) : exprstate(exprstatetype::expr_progress), lhs_{std::move(valex)}, From 8c0ddab587495a23c05c28e001ee060461736fbf Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 00:08:14 -0400 Subject: [PATCH 1417/2524] xo-reader: minor: missed progress_xs refactor to use .start() --- src/reader/progress_xs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 61d1cd77..8d83ad9c 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -348,7 +348,7 @@ namespace xo { std::unique_ptr self = p_stack->pop_exprstate(); /* 3. replace with new progress_xs: */ - p_stack->push_exprstate(progress_xs::make(expr, op2)); + progress_xs::start(expr, op2, p_stack); /* infix operator must be followed by non-empty expression */ expect_expr_xs::start(p_stack); From 79c4b59a193a6b35a89538faff6189b49a60cbc5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 00:10:13 -0400 Subject: [PATCH 1418/2524] xo-reader: refactor: minor streamlining in progress_xs --- src/reader/progress_xs.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 8d83ad9c..407b8b9f 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -369,9 +369,9 @@ namespace xo { std::unique_ptr self = p_stack->pop_exprstate(); /* 1. replace with nested incomplete infix exprs */ - p_stack->push_exprstate(progress_xs::make(lhs_, op_type_)); + progress_xs::start(lhs_, op_type_, p_stack); expect_expr_xs::start(p_stack); - p_stack->push_exprstate(progress_xs::make(rhs_, op2)); + progress_xs::start(rhs_, op2, p_stack); expect_expr_xs::start(p_stack); } From a5e2f622a4fcade434d1b6c5c68a151c7e006385 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 00:13:59 -0400 Subject: [PATCH 1419/2524] xo-reader: refactor: simplify expect_formal_xs api --- include/xo/reader/expect_formal_xs.hpp | 5 ++++- src/reader/expect_formal_arglist_xs.cpp | 4 ++-- src/reader/expect_formal_xs.cpp | 5 +++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/include/xo/reader/expect_formal_xs.hpp b/include/xo/reader/expect_formal_xs.hpp index 79b8bf05..892e447f 100644 --- a/include/xo/reader/expect_formal_xs.hpp +++ b/include/xo/reader/expect_formal_xs.hpp @@ -47,7 +47,7 @@ namespace xo { public: expect_formal_xs(); - static std::unique_ptr make(); + static void start(exprstatestack * p_stack); virtual void on_symbol(const std::string & symbol_name, exprstatestack * p_stack, @@ -71,6 +71,9 @@ namespace xo { virtual void print(std::ostream & os) const override; + private: + static std::unique_ptr make(); + private: /** parsing state-machine state **/ formalstatetype formalxs_type_ = formalstatetype::formal_0; diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index 6f2a4e13..19087cce 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -54,7 +54,7 @@ namespace xo { if (farglxs_type_ == formalarglstatetype::argl_0) { this->farglxs_type_ = formalarglstatetype::argl_1a; /* TODO: refactor to have setup method on each exprstate */ - p_stack->push_exprstate(expect_formal_xs::make()); + expect_formal_xs::start(p_stack); expect_symbol_xs::start(p_stack); } else { exprstate::on_leftparen_token(tk, p_stack, p_emit_expr); @@ -81,7 +81,7 @@ namespace xo { { if (farglxs_type_ == formalarglstatetype::argl_1b) { this->farglxs_type_ = formalarglstatetype::argl_1a; - p_stack->push_exprstate(expect_formal_xs::make()); + expect_formal_xs::start(p_stack); } else { exprstate::on_comma_token(tk, p_stack, p_emit_expr); } diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp index b796a209..31c0ccf9 100644 --- a/src/reader/expect_formal_xs.cpp +++ b/src/reader/expect_formal_xs.cpp @@ -34,6 +34,11 @@ namespace xo { return std::make_unique(expect_formal_xs()); } + void + expect_formal_xs::start(exprstatestack * p_stack) { + p_stack->push_exprstate(expect_formal_xs::make()); + } + expect_formal_xs::expect_formal_xs() : exprstate(exprstatetype::expect_formal) {} From 2df98cc0295e6aaeb2283d4af8701572c3503115 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 00:15:58 -0400 Subject: [PATCH 1420/2524] xo-reader: bugfix: missed expect_symbol_xs.start() + utest to reveal --- src/reader/expect_formal_arglist_xs.cpp | 1 + utest/reader.test.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index 19087cce..469566bc 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -82,6 +82,7 @@ namespace xo { if (farglxs_type_ == formalarglstatetype::argl_1b) { this->farglxs_type_ = formalarglstatetype::argl_1a; expect_formal_xs::start(p_stack); + expect_symbol_xs::start(p_stack); } else { exprstate::on_comma_token(tk, p_stack, p_emit_expr); } diff --git a/utest/reader.test.cpp b/utest/reader.test.cpp index 926b6436..aa85fcc7 100644 --- a/utest/reader.test.cpp +++ b/utest/reader.test.cpp @@ -16,7 +16,8 @@ namespace xo { {"def foo : f64 = 3.14159265;"}, {"def foo : f64 = (3.14159265);"}, //{"def foo : f64 = 2.0 * 3.14159265;"}, - {"def foo = lambda (x : f64) 3.1415965;"} + {"def foo = lambda (x : f64) 3.1415965;"}, + {"def foo = lambda (x : f64, y : f64) 3.1415965;"} }; } From 0dd66a4bcc4d5b6c8784d1f73944081da5ad9133 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 00:17:32 -0400 Subject: [PATCH 1421/2524] xo-reader: refactor: simplify expect_formal_xs api --- src/reader/expect_formal_arglist_xs.cpp | 2 -- src/reader/expect_formal_xs.cpp | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index 469566bc..5b58e3cf 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -55,7 +55,6 @@ namespace xo { this->farglxs_type_ = formalarglstatetype::argl_1a; /* TODO: refactor to have setup method on each exprstate */ expect_formal_xs::start(p_stack); - expect_symbol_xs::start(p_stack); } else { exprstate::on_leftparen_token(tk, p_stack, p_emit_expr); } @@ -82,7 +81,6 @@ namespace xo { if (farglxs_type_ == formalarglstatetype::argl_1b) { this->farglxs_type_ = formalarglstatetype::argl_1a; expect_formal_xs::start(p_stack); - expect_symbol_xs::start(p_stack); } else { exprstate::on_comma_token(tk, p_stack, p_emit_expr); } diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp index 31c0ccf9..3be6bcf8 100644 --- a/src/reader/expect_formal_xs.cpp +++ b/src/reader/expect_formal_xs.cpp @@ -4,6 +4,7 @@ */ #include "expect_formal_xs.hpp" +#include "expect_symbol_xs.hpp" #include "expect_type_xs.hpp" #include "xo/expression/Variable.hpp" @@ -37,6 +38,8 @@ namespace xo { void expect_formal_xs::start(exprstatestack * p_stack) { p_stack->push_exprstate(expect_formal_xs::make()); + + expect_symbol_xs::start(p_stack); } expect_formal_xs::expect_formal_xs() From 7767833afbb172cce963563134c99f66777c445d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 00:24:11 -0400 Subject: [PATCH 1422/2524] xo-reader: refactor: simplify lambda_xs api --- include/xo/reader/lambda_xs.hpp | 5 ++++- src/reader/expect_expr_xs.cpp | 6 +++--- src/reader/lambda_xs.cpp | 9 +++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/include/xo/reader/lambda_xs.hpp b/include/xo/reader/lambda_xs.hpp index 1152406a..e1880039 100644 --- a/include/xo/reader/lambda_xs.hpp +++ b/include/xo/reader/lambda_xs.hpp @@ -40,7 +40,7 @@ namespace xo { public: lambda_xs(); - static std::unique_ptr make(); + static void start(exprstatestack * p_stack, rp * p_emit_expr); virtual void on_lambda_token(const token_type & tk, exprstatestack * p_stack, @@ -55,6 +55,9 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr) override; + private: + static std::unique_ptr make(); + private: /** parsing state-machine state **/ lambdastatetype lmxs_type_ = lambdastatetype::lm_0; diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index c91205d0..9f27d2a0 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -31,7 +31,7 @@ namespace xo { {} void - expect_expr_xs::on_lambda_token(const token_type & tk, + expect_expr_xs::on_lambda_token(const token_type & /*tk*/, exprstatestack * p_stack, rp * p_emit_expr) { @@ -41,8 +41,8 @@ namespace xo { //constexpr const char * self_name = "exprstate::on_leftparen"; /* push lparen_0 to remember to look for subsequent rightparen. */ - p_stack->push_exprstate(lambda_xs::make()); - p_stack->top_exprstate().on_lambda_token(tk, p_stack, p_emit_expr); + lambda_xs::start(p_stack, p_emit_expr); + //p_stack->top_exprstate().on_lambda_token(tk, p_stack, p_emit_expr); //p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); } diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index c9c131ac..1b28cf11 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -14,6 +14,15 @@ namespace xo { return std::make_unique(lambda_xs()); } + void + lambda_xs::start(exprstatestack * p_stack, + rp * p_emit_expr) + { + p_stack->push_exprstate(lambda_xs::make()); + p_stack->top_exprstate() + .on_lambda_token(token_type::lambda(), p_stack, p_emit_expr); + } + lambda_xs::lambda_xs() : exprstate(exprstatetype::lambdaexpr) {} void From 6a71f718bda97fb8ffb1fc7eb75217541316ecfc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 00:27:35 -0400 Subject: [PATCH 1423/2524] xo-reader: refactor: simplify paren_xs api --- include/xo/reader/paren_xs.hpp | 5 ++++- src/reader/expect_expr_xs.cpp | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 65811607..dada4627 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -29,7 +29,7 @@ namespace xo { static const paren_xs * from(const exprstate * x) { return dynamic_cast(x); } - static std::unique_ptr lparen_0(); + static void start(exprstatestack * p_stack); bool admits_f64() const; bool admits_rightparen() const; @@ -68,6 +68,9 @@ namespace xo { virtual void print(std::ostream & os) const override; + private: + static std::unique_ptr make(); + private: /** * ( foo ... ) diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index 9f27d2a0..8ecc2e5b 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -57,8 +57,7 @@ namespace xo { //constexpr const char * self_name = "exprstate::on_leftparen"; /* push lparen_0 to remember to look for subsequent rightparen. */ - p_stack->push_exprstate(paren_xs::lparen_0()); - expect_expr_xs::start(p_stack); + paren_xs::start(p_stack); } void From 034dac7dfdd946efe021dcceeaff38245156098c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 00:44:34 -0400 Subject: [PATCH 1424/2524] xo-reader: refactor: + parserstatemachine; use for def_expr --- include/xo/reader/define_xs.hpp | 4 +-- include/xo/reader/exprseq_xs.hpp | 2 +- include/xo/reader/exprstate.hpp | 3 +- include/xo/reader/paren_xs.hpp | 2 +- include/xo/reader/parserstatemachine.hpp | 36 ++++++++++++++++++++++++ include/xo/reader/progress_xs.hpp | 2 +- src/reader/define_xs.cpp | 13 +++++---- src/reader/exprseq_xs.cpp | 4 +-- src/reader/exprstate.cpp | 7 +++-- src/reader/paren_xs.cpp | 12 ++++++-- src/reader/progress_xs.cpp | 2 +- 11 files changed, 69 insertions(+), 18 deletions(-) create mode 100644 include/xo/reader/parserstatemachine.hpp diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 67c53878..dad682c3 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -70,7 +70,7 @@ namespace xo { static const define_xs * from(const exprstate * x) { return dynamic_cast(x); } - static void start(exprstatestack * p_stack); + static void start(parserstatemachine * p_psm); defexprstatetype defxs_type() const { return defxs_type_; } @@ -84,7 +84,7 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr) override; virtual void on_def_token(const token_type & tk, - exprstatestack * p_stack) override; + parserstatemachine * p_psm) override; virtual void on_colon_token(const token_type & tk, exprstatestack * p_stack) override; virtual void on_semicolon_token(const token_type & tk, diff --git a/include/xo/reader/exprseq_xs.hpp b/include/xo/reader/exprseq_xs.hpp index 1797855a..6955e8a6 100644 --- a/include/xo/reader/exprseq_xs.hpp +++ b/include/xo/reader/exprseq_xs.hpp @@ -24,7 +24,7 @@ namespace xo { // ----- token input methods ----- virtual void on_def_token(const token_type & tk, - exprstatestack * p_stack) override; + parserstatemachine * p_psm) override; virtual void on_symbol_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) override; diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 5d3ee107..fb541cd9 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -63,6 +63,7 @@ namespace xo { return os; } + class parserstatemachine; class exprstatestack; class formal_arg; @@ -120,7 +121,7 @@ namespace xo { /** handle incoming 'def' token **/ virtual void on_def_token(const token_type & tk, - exprstatestack * p_stack); + parserstatemachine * p_psm); /** handle incoming 'lambda' token **/ virtual void on_lambda_token(const token_type & tk, exprstatestack * p_stack, diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index dada4627..48cc6ea8 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -45,7 +45,7 @@ namespace xo { rp * /*p_emit_expr*/) override; virtual void on_def_token(const token_type & tk, - exprstatestack * p_stack) override; + parserstatemachine * p_psm) override; virtual void on_symbol_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) override; diff --git a/include/xo/reader/parserstatemachine.hpp b/include/xo/reader/parserstatemachine.hpp new file mode 100644 index 00000000..55b285ee --- /dev/null +++ b/include/xo/reader/parserstatemachine.hpp @@ -0,0 +1,36 @@ +/* file parserstatemachine.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "exprstate.hpp" + +namespace xo { + namespace scm { + /** @class parserstatemachine + * @brief public parser state. + * + * Schematica parser state; sent to subsidiary single-feature state machines. + * For example entry points for the lambda feature (@ref lambda_xs) + * will accept a non-const parserstatemachine pointer argument + **/ + class parserstatemachine { + public: + using Expression = xo::ast::Expression; + + public: + parserstatemachine(exprstatestack * p_stack, + rp * p_emit_expr) + : p_stack_{p_stack}, p_emit_expr_{p_emit_expr} {} + + public: + exprstatestack * p_stack_; + rp * p_emit_expr_; + }; + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end parserstatemachine.hpp */ diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 466b4b02..6efe21eb 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -66,7 +66,7 @@ namespace xo { rp * /*p_emit_expr*/) override; virtual void on_def_token(const token_type & tk, - exprstatestack * p_stack) override; + parserstatemachine * p_psm) override; virtual void on_colon_token(const token_type & tk, exprstatestack * p_stack) override; virtual void on_semicolon_token(const token_type & tk, diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 20ffbd40..a141bfb8 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -1,6 +1,7 @@ /* @file define_xs.cpp */ #include "define_xs.hpp" +#include "parserstatemachine.hpp" #include "expect_symbol_xs.hpp" #include "expect_expr_xs.hpp" #include "expect_type_xs.hpp" @@ -13,10 +14,12 @@ namespace xo { } void - define_xs::start(exprstatestack * p_stack) + define_xs::start(parserstatemachine * p_psm) { + auto p_stack = p_psm->p_stack_; + p_stack->push_exprstate(define_xs::make()); - p_stack->top_exprstate().on_def_token(token_type::def(), p_stack); + p_stack->top_exprstate().on_def_token(token_type::def(), p_psm); } define_xs::define_xs(rp def_expr) @@ -88,7 +91,7 @@ namespace xo { void define_xs::on_def_token(const token_type & tk, - exprstatestack * p_stack) + parserstatemachine * p_psm) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -98,9 +101,9 @@ namespace xo { if (this->defxs_type_ == defexprstatetype::def_0) { this->defxs_type_ = defexprstatetype::def_1; - expect_symbol_xs::start(p_stack); + expect_symbol_xs::start(p_psm->p_stack_); } else { - exprstate::on_def_token(tk, p_stack); + exprstate::on_def_token(tk, p_psm); } } diff --git a/src/reader/exprseq_xs.cpp b/src/reader/exprseq_xs.cpp index 840cd958..25873606 100644 --- a/src/reader/exprseq_xs.cpp +++ b/src/reader/exprseq_xs.cpp @@ -25,14 +25,14 @@ namespace xo { void exprseq_xs::on_def_token(const token_type & /*tk*/, - exprstatestack * p_stack) + parserstatemachine * p_psm) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); //constexpr const char * c_self_name = "exprseq_xs::on_def_token"; - define_xs::start(p_stack); + define_xs::start(p_psm); /* keyword 'def' introduces a definition: * def pi : f64 = 3.14159265 diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 8352914a..71f0eb50 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -1,6 +1,7 @@ /* @file exprstate.cpp */ #include "exprstate.hpp" +#include "parserstatemachine.hpp" //#include "formal_arg.hpp" #include "xo/expression/Variable.hpp" #include "xo/indentlog/print/vector.hpp" @@ -52,7 +53,7 @@ namespace xo { void exprstate::on_def_token(const token_type & tk, - exprstatestack * /*p_stack*/) + parserstatemachine * /*p_psm*/) { this->illegal_input_error("exprstate::on_def_token", tk); } @@ -254,10 +255,12 @@ namespace xo { log && log(xtag("tk", tk)); log && log(xtag("state", *this)); + parserstatemachine psm(p_stack, p_emit_expr); + switch (tk.tk_type()) { case tokentype::tk_def: - this->on_def_token(tk, p_stack); + this->on_def_token(tk, &psm); return; case tokentype::tk_lambda: diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 06329bde..906a9b18 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -2,6 +2,7 @@ #include "paren_xs.hpp" #include "progress_xs.hpp" +#include "expect_expr_xs.hpp" namespace xo { namespace scm { @@ -10,10 +11,17 @@ namespace xo { {} std::unique_ptr - paren_xs::lparen_0() { + paren_xs::make() { return std::make_unique(paren_xs()); } + void + paren_xs::start(exprstatestack * p_stack) + { + p_stack->push_exprstate(paren_xs::make()); + expect_expr_xs::start(p_stack); + } + bool paren_xs::admits_rightparen() const { switch (parenxs_type_) { @@ -56,7 +64,7 @@ namespace xo { void paren_xs::on_def_token(const token_type & tk, - exprstatestack * /*p_stack*/) + parserstatemachine * /*p_stack*/) { constexpr const char * c_self_name = "paren_xs::on_def"; diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 407b8b9f..481cb895 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -73,7 +73,7 @@ namespace xo { void progress_xs::on_def_token(const token_type & tk, - exprstatestack * /*p_stack*/) + parserstatemachine * /*p_stack*/) { constexpr const char * self_name = "progress_xs::on_def"; From e5dc8d14d42e5a4066d5427a7ac9a819b765c5cb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 00:48:53 -0400 Subject: [PATCH 1425/2524] xo-reader: refactor: use parserstatemachine for parser.on_input() --- include/xo/reader/exprstate.hpp | 5 ++--- src/reader/exprstate.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index fb541cd9..e36972fa 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -63,7 +63,7 @@ namespace xo { return os; } - class parserstatemachine; + class parserstatemachine; /* see parserstatemachine.hpp */ class exprstatestack; class formal_arg; @@ -91,8 +91,7 @@ namespace xo { * forward instructions to parent parser **/ void on_input(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); + parserstatemachine * p_psm); /** update exprstate in response to a successfully-parsed subexpression **/ virtual void on_expr(ref::brw expr, diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 71f0eb50..d5f72881 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -247,20 +247,20 @@ namespace xo { void exprstate::on_input(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); log && log(xtag("tk", tk)); log && log(xtag("state", *this)); - parserstatemachine psm(p_stack, p_emit_expr); + auto p_stack = p_psm->p_stack_; + auto p_emit_expr = p_psm->p_emit_expr_; switch (tk.tk_type()) { case tokentype::tk_def: - this->on_def_token(tk, &psm); + this->on_def_token(tk, p_psm); return; case tokentype::tk_lambda: From 8cae38817bd7cecbf541809a52e57cfd59ec4527 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 00:54:45 -0400 Subject: [PATCH 1426/2524] xo-reader: refactor: parserstatemachine w/ exprstate.on_lambda_token --- include/xo/reader/expect_expr_xs.hpp | 3 +-- include/xo/reader/exprstate.hpp | 3 +-- include/xo/reader/lambda_xs.hpp | 3 +-- src/reader/expect_expr_xs.cpp | 7 +++++-- src/reader/exprstate.cpp | 5 ++--- src/reader/lambda_xs.cpp | 12 ++++++++---- src/reader/parser.cpp | 5 ++++- 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/include/xo/reader/expect_expr_xs.hpp b/include/xo/reader/expect_expr_xs.hpp index 85686d96..f7bcb097 100644 --- a/include/xo/reader/expect_expr_xs.hpp +++ b/include/xo/reader/expect_expr_xs.hpp @@ -20,8 +20,7 @@ namespace xo { static void start(exprstatestack * p_stack); virtual void on_lambda_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_leftparen_token(const token_type & tk, exprstatestack * p_stack, rp * p_emit_expr) override; diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index e36972fa..f0bbf2f3 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -123,8 +123,7 @@ namespace xo { parserstatemachine * p_psm); /** handle incoming 'lambda' token **/ virtual void on_lambda_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); + parserstatemachine * p_psm); /** handle incoming symbol token **/ virtual void on_symbol_token(const token_type & tk, exprstatestack * p_stack, diff --git a/include/xo/reader/lambda_xs.hpp b/include/xo/reader/lambda_xs.hpp index e1880039..7597e937 100644 --- a/include/xo/reader/lambda_xs.hpp +++ b/include/xo/reader/lambda_xs.hpp @@ -43,8 +43,7 @@ namespace xo { static void start(exprstatestack * p_stack, rp * p_emit_expr); virtual void on_lambda_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_formal_arglist(const std::vector> & argl, exprstatestack * p_stack, rp * p_emit_expr) override; diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index 8ecc2e5b..bb90047f 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -4,6 +4,7 @@ */ #include "expect_expr_xs.hpp" +#include "parserstatemachine.hpp" #include "lambda_xs.hpp" #include "paren_xs.hpp" #include "progress_xs.hpp" @@ -32,14 +33,16 @@ namespace xo { void expect_expr_xs::on_lambda_token(const token_type & /*tk*/, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); //constexpr const char * self_name = "exprstate::on_leftparen"; + auto p_stack = p_psm->p_stack_; + auto p_emit_expr = p_psm->p_emit_expr_; + /* push lparen_0 to remember to look for subsequent rightparen. */ lambda_xs::start(p_stack, p_emit_expr); //p_stack->top_exprstate().on_lambda_token(tk, p_stack, p_emit_expr); diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index d5f72881..cd1342ff 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -60,8 +60,7 @@ namespace xo { void exprstate::on_lambda_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { this->illegal_input_error("exprstate::on_lambda_token", tk); } @@ -264,7 +263,7 @@ namespace xo { return; case tokentype::tk_lambda: - this->on_lambda_token(tk, p_stack, p_emit_expr); + this->on_lambda_token(tk, p_psm); return; case tokentype::tk_i64: diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index 1b28cf11..f861d1aa 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -1,6 +1,7 @@ /* @file lambda_xs.cpp */ #include "lambda_xs.hpp" +#include "parserstatemachine.hpp" #include "expect_formal_arglist_xs.hpp" #include "expect_expr_xs.hpp" #include "xo/expression/Lambda.hpp" @@ -18,23 +19,26 @@ namespace xo { lambda_xs::start(exprstatestack * p_stack, rp * p_emit_expr) { + parserstatemachine psm(p_stack, p_emit_expr); + p_stack->push_exprstate(lambda_xs::make()); p_stack->top_exprstate() - .on_lambda_token(token_type::lambda(), p_stack, p_emit_expr); + .on_lambda_token(token_type::lambda(), &psm); } lambda_xs::lambda_xs() : exprstate(exprstatetype::lambdaexpr) {} void lambda_xs::on_lambda_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { + auto p_stack = p_psm->p_stack_; + if (lmxs_type_ == lambdastatetype::lm_0) { this->lmxs_type_ = lambdastatetype::lm_1; expect_formal_arglist_xs::start(p_stack); } else { - exprstate::on_lambda_token(tk, p_stack, p_emit_expr); + exprstate::on_lambda_token(tk, p_psm); } } diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index 5c2a99bb..9c9d373e 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -4,6 +4,7 @@ */ #include "parser.hpp" +#include "parserstatemachine.hpp" #include "define_xs.hpp" #include "exprseq_xs.hpp" #include "xo/expression/DefineExpr.hpp" @@ -50,7 +51,9 @@ namespace xo { rp retval; - xs_stack_.top_exprstate().on_input(tk, &xs_stack_, &retval); + parserstatemachine psm(&xs_stack_, &retval); + + xs_stack_.top_exprstate().on_input(tk, &psm); log && log(xtag("retval", retval)); From bda115037bada94905b038e34e90fd9472000257 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 01:03:18 -0400 Subject: [PATCH 1427/2524] xo-reader: refactor: use parsestatemachine w/ on_symbol_token() --- include/xo/reader/expect_expr_xs.hpp | 3 +-- include/xo/reader/expect_symbol_xs.hpp | 3 +-- include/xo/reader/expect_type_xs.hpp | 3 +-- include/xo/reader/exprseq_xs.hpp | 3 +-- include/xo/reader/exprstate.hpp | 3 +-- include/xo/reader/paren_xs.hpp | 3 +-- include/xo/reader/progress_xs.hpp | 3 +-- src/reader/expect_expr_xs.cpp | 3 +-- src/reader/expect_symbol_xs.cpp | 8 ++++++-- src/reader/expect_type_xs.cpp | 8 +++++--- src/reader/exprseq_xs.cpp | 3 +-- src/reader/exprstate.cpp | 7 ++++--- src/reader/paren_xs.cpp | 8 +++++--- src/reader/progress_xs.cpp | 3 +-- 14 files changed, 30 insertions(+), 31 deletions(-) diff --git a/include/xo/reader/expect_expr_xs.hpp b/include/xo/reader/expect_expr_xs.hpp index f7bcb097..0259e6a6 100644 --- a/include/xo/reader/expect_expr_xs.hpp +++ b/include/xo/reader/expect_expr_xs.hpp @@ -26,8 +26,7 @@ namespace xo { rp * p_emit_expr) override; virtual void on_symbol_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_f64_token(const token_type & tk, exprstatestack * p_stack, diff --git a/include/xo/reader/expect_symbol_xs.hpp b/include/xo/reader/expect_symbol_xs.hpp index 40cb25b6..63740aeb 100644 --- a/include/xo/reader/expect_symbol_xs.hpp +++ b/include/xo/reader/expect_symbol_xs.hpp @@ -23,8 +23,7 @@ namespace xo { static void start(exprstatestack * p_stack); virtual void on_symbol_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/include/xo/reader/expect_type_xs.hpp b/include/xo/reader/expect_type_xs.hpp index 0e534a2a..86e5adf7 100644 --- a/include/xo/reader/expect_type_xs.hpp +++ b/include/xo/reader/expect_type_xs.hpp @@ -19,8 +19,7 @@ namespace xo { static void start(exprstatestack * p_stack); virtual void on_symbol_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; private: static std::unique_ptr make(); diff --git a/include/xo/reader/exprseq_xs.hpp b/include/xo/reader/exprseq_xs.hpp index 6955e8a6..a5576001 100644 --- a/include/xo/reader/exprseq_xs.hpp +++ b/include/xo/reader/exprseq_xs.hpp @@ -26,8 +26,7 @@ namespace xo { virtual void on_def_token(const token_type & tk, parserstatemachine * p_psm) override; virtual void on_symbol_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; // ----- victory methods ----- diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index f0bbf2f3..ab091fdc 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -126,8 +126,7 @@ namespace xo { parserstatemachine * p_psm); /** handle incoming symbol token **/ virtual void on_symbol_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); + parserstatemachine * p_psm); /** handle incoming ',' token **/ virtual void on_comma_token(const token_type & tk, exprstatestack * p_stack, diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 48cc6ea8..9e8c8dff 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -47,8 +47,7 @@ namespace xo { virtual void on_def_token(const token_type & tk, parserstatemachine * p_psm) override; virtual void on_symbol_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_colon_token(const token_type & tk, exprstatestack * p_stack) override; virtual void on_semicolon_token(const token_type & tk, diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 6efe21eb..06107bad 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -59,8 +59,7 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr) override; virtual void on_symbol_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_typedescr(TypeDescr td, exprstatestack * /*p_stack*/, rp * /*p_emit_expr*/) override; diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index bb90047f..9e28eb6e 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -65,8 +65,7 @@ namespace xo { void expect_expr_xs::on_symbol_token(const token_type & /*tk*/, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { /* todo: treat symbol as variable name */ diff --git a/src/reader/expect_symbol_xs.cpp b/src/reader/expect_symbol_xs.cpp index e6fd2bb7..12334e73 100644 --- a/src/reader/expect_symbol_xs.cpp +++ b/src/reader/expect_symbol_xs.cpp @@ -4,6 +4,7 @@ */ #include "expect_symbol_xs.hpp" +#include "parserstatemachine.hpp" namespace xo { namespace scm { @@ -24,14 +25,17 @@ namespace xo { void expect_symbol_xs::on_symbol_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { + auto p_stack = p_psm->p_stack_; + auto p_emit_expr = p_psm->p_emit_expr_; + /* have to do pop first, before sending symbol to * the o.g. symbol-requester */ std::unique_ptr self = p_stack->pop_exprstate(); + p_stack->top_exprstate().on_symbol(tk.text(), p_stack, p_emit_expr); return; diff --git a/src/reader/expect_type_xs.cpp b/src/reader/expect_type_xs.cpp index add0dc77..11ab04ed 100644 --- a/src/reader/expect_type_xs.cpp +++ b/src/reader/expect_type_xs.cpp @@ -4,7 +4,7 @@ */ #include "expect_type_xs.hpp" -#include "exprstate.hpp" +#include "parserstatemachine.hpp" #include "xo/reflect/Reflect.hpp" namespace xo { @@ -28,11 +28,13 @@ namespace xo { void expect_type_xs::on_symbol_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { const char * c_self_name = "expect_type_xs::on_symbol_token"; + auto p_stack = p_psm->p_stack_; + auto p_emit_expr = p_psm->p_emit_expr_; + TypeDescr td = nullptr; /* TODO: replace with typetable lookup */ diff --git a/src/reader/exprseq_xs.cpp b/src/reader/exprseq_xs.cpp index 25873606..76722e5b 100644 --- a/src/reader/exprseq_xs.cpp +++ b/src/reader/exprseq_xs.cpp @@ -42,8 +42,7 @@ namespace xo { void exprseq_xs::on_symbol_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { constexpr const char * c_self_name = "exprseq_xs::on_symbol_token"; diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index cd1342ff..97be6fec 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -67,12 +67,13 @@ namespace xo { void exprstate::on_symbol_token(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) + parserstatemachine * p_psm) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); + auto p_stack = p_psm->p_stack_; + log && log(xtag("exstype", p_stack->top_exprstate().exs_type())); constexpr const char * c_self_name = "exprstate::on_symbol_token"; @@ -279,7 +280,7 @@ namespace xo { return; case tokentype::tk_symbol: - this->on_symbol_token(tk, p_stack, p_emit_expr); + this->on_symbol_token(tk, p_psm); return; case tokentype::tk_leftparen: diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 906a9b18..9ec0a0b8 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -1,6 +1,7 @@ /* @file paren_xs.cpp */ #include "paren_xs.hpp" +#include "parserstatemachine.hpp" #include "progress_xs.hpp" #include "expect_expr_xs.hpp" @@ -64,7 +65,7 @@ namespace xo { void paren_xs::on_def_token(const token_type & tk, - parserstatemachine * /*p_stack*/) + parserstatemachine * /*p_psm*/) { constexpr const char * c_self_name = "paren_xs::on_def"; @@ -73,12 +74,13 @@ namespace xo { void paren_xs::on_symbol_token(const token_type & /*tk*/, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) + parserstatemachine * p_psm) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); + auto p_stack = p_psm->p_stack_; + log && log(xtag("exstype", p_stack->top_exprstate().exs_type())); //constexpr const char * self_name = "paren_xs::on_symbol"; diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 481cb895..431b0cb8 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -164,8 +164,7 @@ namespace xo { void progress_xs::on_symbol_token(const token_type & /*tk*/, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { /* illegal input, e.g. * foo bar From b02d1e17e4041545e518953ed5415b33481f1231 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 11:39:27 -0400 Subject: [PATCH 1428/2524] xo-reader: refactor: parserstatemachine to consolidate on_input() --- include/xo/reader/define_xs.hpp | 13 +++--- include/xo/reader/expect_expr_xs.hpp | 7 ++-- .../xo/reader/expect_formal_arglist_xs.hpp | 9 ++-- include/xo/reader/expect_formal_xs.hpp | 3 +- include/xo/reader/exprstate.hpp | 24 +++++------ include/xo/reader/lambda_xs.hpp | 3 +- include/xo/reader/paren_xs.hpp | 16 +++----- include/xo/reader/progress_xs.hpp | 19 ++++----- src/reader/define_xs.cpp | 24 ++++++----- src/reader/expect_expr_xs.cpp | 10 ++--- src/reader/expect_formal_arglist_xs.cpp | 23 +++++++---- src/reader/expect_formal_xs.cpp | 8 ++-- src/reader/exprstate.cpp | 41 ++++++++----------- src/reader/lambda_xs.cpp | 10 +++-- src/reader/paren_xs.cpp | 26 +++++------- src/reader/progress_xs.cpp | 36 ++++++++-------- 16 files changed, 124 insertions(+), 148 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index dad682c3..9e616b31 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -86,18 +86,15 @@ namespace xo { virtual void on_def_token(const token_type & tk, parserstatemachine * p_psm) override; virtual void on_colon_token(const token_type & tk, - exprstatestack * p_stack) override; + parserstatemachine * p_psm) override; virtual void on_semicolon_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_singleassign_token(const token_type & tk, - exprstatestack * p_stack) override; + parserstatemachine * p_psm) override; virtual void on_rightparen_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_f64_token(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + parserstatemachine * p_psm) override; virtual void print(std::ostream & os) const override; diff --git a/include/xo/reader/expect_expr_xs.hpp b/include/xo/reader/expect_expr_xs.hpp index 0259e6a6..3808e7bc 100644 --- a/include/xo/reader/expect_expr_xs.hpp +++ b/include/xo/reader/expect_expr_xs.hpp @@ -21,16 +21,15 @@ namespace xo { virtual void on_lambda_token(const token_type & tk, parserstatemachine * p_psm) override; + virtual void on_leftparen_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_symbol_token(const token_type & tk, parserstatemachine * p_psm) override; virtual void on_f64_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; /** update exprstate in response to a successfully-parsed subexpression **/ virtual void on_expr(ref::brw expr, diff --git a/include/xo/reader/expect_formal_arglist_xs.hpp b/include/xo/reader/expect_formal_arglist_xs.hpp index 442dde62..7eef773d 100644 --- a/include/xo/reader/expect_formal_arglist_xs.hpp +++ b/include/xo/reader/expect_formal_arglist_xs.hpp @@ -56,17 +56,14 @@ namespace xo { static void start(exprstatestack * p_stack); virtual void on_leftparen_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_formal(const rp & formal, exprstatestack * p_stack, rp * p_emit_expr) override; virtual void on_comma_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_rightparen_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void print(std::ostream & os) const override; private: diff --git a/include/xo/reader/expect_formal_xs.hpp b/include/xo/reader/expect_formal_xs.hpp index 892e447f..b757f6f5 100644 --- a/include/xo/reader/expect_formal_xs.hpp +++ b/include/xo/reader/expect_formal_xs.hpp @@ -54,8 +54,7 @@ namespace xo { rp * p_emit_expr) override; virtual void on_colon_token(const token_type & tk, - exprstatestack * p_stack - /*rp * p_emit_expr*/) override; + parserstatemachine * p_psm) override; // virtual void on_comma_token(...) override; diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index ab091fdc..bf93a3f5 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -129,34 +129,30 @@ namespace xo { parserstatemachine * p_psm); /** handle incoming ',' token **/ virtual void on_comma_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); + parserstatemachine * p_psm); /** handle incoming ':' token **/ virtual void on_colon_token(const token_type & tk, - exprstatestack * p_stack); + parserstatemachine * p_psm); /** handle incoming ';' token **/ virtual void on_semicolon_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); + parserstatemachine * p_psm); /** handle incoming '=' token **/ virtual void on_singleassign_token(const token_type & tk, - exprstatestack * p_stack); + parserstatemachine * p_psm); /** handle incoming '(' token **/ virtual void on_leftparen_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); + parserstatemachine * p_psm); /** handle incoming ')' token **/ virtual void on_rightparen_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); + parserstatemachine * p_psm); + /** handle incoming operator token **/ virtual void on_operator_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); + parserstatemachine * p_psm); + /** handle incoming floating-point-literal token **/ virtual void on_f64_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr); + parserstatemachine * p_psm); protected: /** throw exception when next token is inconsistent with diff --git a/include/xo/reader/lambda_xs.hpp b/include/xo/reader/lambda_xs.hpp index 7597e937..59f9f78a 100644 --- a/include/xo/reader/lambda_xs.hpp +++ b/include/xo/reader/lambda_xs.hpp @@ -51,8 +51,7 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr) override; virtual void on_semicolon_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; private: static std::unique_ptr make(); diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 9e8c8dff..b4225f4e 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -49,21 +49,17 @@ namespace xo { virtual void on_symbol_token(const token_type & tk, parserstatemachine * p_psm) override; virtual void on_colon_token(const token_type & tk, - exprstatestack * p_stack) override; + parserstatemachine * p_psm) override; virtual void on_semicolon_token(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + parserstatemachine * p_psm) override; virtual void on_singleassign_token(const token_type & tk, - exprstatestack * p_stack) override; + parserstatemachine * p_psm) override; virtual void on_leftparen_token(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + parserstatemachine * p_psm) override; virtual void on_rightparen_token(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + parserstatemachine * p_psm) override; virtual void on_f64_token(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + parserstatemachine * p_psm) override; virtual void print(std::ostream & os) const override; diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 06107bad..965da117 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -67,27 +67,22 @@ namespace xo { virtual void on_def_token(const token_type & tk, parserstatemachine * p_psm) override; virtual void on_colon_token(const token_type & tk, - exprstatestack * p_stack) override; + parserstatemachine * p_psm) override; virtual void on_semicolon_token(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + parserstatemachine * p_psm) override; virtual void on_singleassign_token(const token_type & tk, - exprstatestack * p_stack) override; + parserstatemachine * p_psm) override; virtual void on_leftparen_token(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + parserstatemachine * p_psm) override; virtual void on_rightparen_token(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + parserstatemachine * p_psm) override; /* entry point for an infix operator token */ virtual void on_operator_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_f64_token(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) override; + parserstatemachine * p_psm) override; virtual void print(std::ostream & os) const override; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index a141bfb8..0f5561dc 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -109,30 +109,34 @@ namespace xo { void define_xs::on_colon_token(const token_type & tk, - exprstatestack * p_stack) + parserstatemachine * p_psm) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); //constexpr const char * self_name = "define_xs::on_colon_token"; + auto p_stack = p_psm->p_stack_; + if (this->defxs_type_ == defexprstatetype::def_2) { this->defxs_type_ = defexprstatetype::def_3; expect_type_xs::start(p_stack); } else { - exprstate::on_colon_token(tk, p_stack); + exprstate::on_colon_token(tk, p_psm); } } void define_xs::on_semicolon_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); + auto p_stack = p_psm->p_stack_; + auto p_emit_expr = p_psm->p_emit_expr_; + //constexpr const char * self_name = "exprstate::on_semicolon"; if (this->defxs_type_ == defexprstatetype::def_6) { @@ -144,13 +148,13 @@ namespace xo { p_stack, p_emit_expr); } else { - exprstate::on_semicolon_token(tk, p_stack, p_emit_expr); + exprstate::on_semicolon_token(tk, p_psm); } } void define_xs::on_singleassign_token(const token_type & tk, - exprstatestack * p_stack) + parserstatemachine * p_psm) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -177,7 +181,7 @@ namespace xo { { this->defxs_type_ = defexprstatetype::def_5; - expect_expr_xs::start(p_stack); + expect_expr_xs::start(p_psm->p_stack_); } else { this->illegal_input_error(self_name, tk); } @@ -185,8 +189,7 @@ namespace xo { void define_xs::on_rightparen_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -198,8 +201,7 @@ namespace xo { void define_xs::on_f64_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index 9e28eb6e..123f52da 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -51,14 +51,15 @@ namespace xo { void expect_expr_xs::on_leftparen_token(const token_type & /*tk*/, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) + parserstatemachine * p_psm) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); //constexpr const char * self_name = "exprstate::on_leftparen"; + auto p_stack = p_psm->p_stack_; + /* push lparen_0 to remember to look for subsequent rightparen. */ paren_xs::start(p_stack); } @@ -99,8 +100,7 @@ namespace xo { void expect_expr_xs::on_f64_token(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) + parserstatemachine * p_psm) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -113,7 +113,7 @@ namespace xo { */ progress_xs::start (Constant::make(tk.f64_value()), - p_stack); + p_psm->p_stack_); } void diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index 5b58e3cf..6bb9b099 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -6,6 +6,7 @@ #include "expect_formal_arglist_xs.hpp" #include "expect_formal_xs.hpp" #include "expect_symbol_xs.hpp" +#include "parserstatemachine.hpp" #include "xo/expression/Variable.hpp" #include "xo/indentlog/print/vector.hpp" @@ -48,15 +49,16 @@ namespace xo { void expect_formal_arglist_xs::on_leftparen_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { + auto p_stack = p_psm->p_stack_; + if (farglxs_type_ == formalarglstatetype::argl_0) { this->farglxs_type_ = formalarglstatetype::argl_1a; /* TODO: refactor to have setup method on each exprstate */ expect_formal_xs::start(p_stack); } else { - exprstate::on_leftparen_token(tk, p_stack, p_emit_expr); + exprstate::on_leftparen_token(tk, p_psm); } } @@ -75,29 +77,32 @@ namespace xo { void expect_formal_arglist_xs::on_comma_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { + auto p_stack = p_psm->p_stack_; + if (farglxs_type_ == formalarglstatetype::argl_1b) { this->farglxs_type_ = formalarglstatetype::argl_1a; expect_formal_xs::start(p_stack); } else { - exprstate::on_comma_token(tk, p_stack, p_emit_expr); + exprstate::on_comma_token(tk, p_psm); } } void expect_formal_arglist_xs::on_rightparen_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { + auto p_stack = p_psm->p_stack_; + auto p_emit_expr = p_psm->p_emit_expr_; + if (farglxs_type_ == formalarglstatetype::argl_1b) { std::unique_ptr self = p_stack->pop_exprstate(); p_stack->top_exprstate().on_formal_arglist(this->argl_, p_stack, p_emit_expr); } else { - exprstate::on_rightparen_token(tk, p_stack, p_emit_expr); + exprstate::on_rightparen_token(tk, p_psm); } } diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp index 3be6bcf8..3455de23 100644 --- a/src/reader/expect_formal_xs.cpp +++ b/src/reader/expect_formal_xs.cpp @@ -6,6 +6,7 @@ #include "expect_formal_xs.hpp" #include "expect_symbol_xs.hpp" #include "expect_type_xs.hpp" +#include "parserstatemachine.hpp" #include "xo/expression/Variable.hpp" namespace xo { @@ -63,16 +64,17 @@ namespace xo { void expect_formal_xs::on_colon_token(const token_type & tk, - exprstatestack * p_stack - /* rp * p_emit_expr */) + parserstatemachine * p_psm) { + auto p_stack = p_psm->p_stack_; + if (this->formalxs_type_ == formalstatetype::formal_1) { this->formalxs_type_ = formalstatetype::formal_2; expect_type_xs::start(p_stack); /* control reenters via expect_formal_xs::on_typedescr() */ } else { exprstate::on_colon_token(tk, - p_stack); + p_psm); } } diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 97be6fec..ba58eb79 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -146,7 +146,7 @@ namespace xo { void exprstate::on_colon_token(const token_type & tk, - exprstatestack * /*p_stack*/) + parserstatemachine * /*p_psm*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -158,8 +158,7 @@ namespace xo { void exprstate::on_comma_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -171,8 +170,7 @@ namespace xo { void exprstate::on_semicolon_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -184,7 +182,7 @@ namespace xo { void exprstate::on_singleassign_token(const token_type & tk, - exprstatestack * /*p_stack*/) { + parserstatemachine * /*p_psm*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -195,8 +193,7 @@ namespace xo { void exprstate::on_leftparen_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -208,8 +205,7 @@ namespace xo { void exprstate::on_rightparen_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -221,8 +217,7 @@ namespace xo { void exprstate::on_operator_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -234,8 +229,7 @@ namespace xo { void exprstate::on_f64_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -254,9 +248,6 @@ namespace xo { log && log(xtag("tk", tk)); log && log(xtag("state", *this)); - auto p_stack = p_psm->p_stack_; - auto p_emit_expr = p_psm->p_emit_expr_; - switch (tk.tk_type()) { case tokentype::tk_def: @@ -272,7 +263,7 @@ namespace xo { return; case tokentype::tk_f64: - this->on_f64_token(tk, p_stack, p_emit_expr); + this->on_f64_token(tk, p_psm); return; case tokentype::tk_string: @@ -284,11 +275,11 @@ namespace xo { return; case tokentype::tk_leftparen: - this->on_leftparen_token(tk, p_stack, p_emit_expr); + this->on_leftparen_token(tk, p_psm); return; case tokentype::tk_rightparen: - this->on_rightparen_token(tk, p_stack, p_emit_expr); + this->on_rightparen_token(tk, p_psm); return; case tokentype::tk_leftbracket: @@ -303,11 +294,11 @@ namespace xo { return; case tokentype::tk_comma: - this->on_comma_token(tk, p_stack, p_emit_expr); + this->on_comma_token(tk, p_psm); return; case tokentype::tk_colon: - this->on_colon_token(tk, p_stack); + this->on_colon_token(tk, p_psm); return; case tokentype::tk_doublecolon: @@ -315,11 +306,11 @@ namespace xo { return; case tokentype::tk_semicolon: - this->on_semicolon_token(tk, p_stack, p_emit_expr); + this->on_semicolon_token(tk, p_psm); return; case tokentype::tk_singleassign: - this->on_singleassign_token(tk, p_stack); + this->on_singleassign_token(tk, p_psm); return; case tokentype::tk_assign: @@ -329,7 +320,7 @@ namespace xo { case tokentype::tk_minus: case tokentype::tk_star: case tokentype::tk_slash: - this->on_operator_token(tk, p_stack, p_emit_expr); + this->on_operator_token(tk, p_psm); return; case tokentype::tk_type: diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index f861d1aa..8824ab26 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -71,9 +71,11 @@ namespace xo { void lambda_xs::on_semicolon_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { + auto p_stack = p_psm->p_stack_; + auto p_emit_expr = p_psm->p_emit_expr_; + if (lmxs_type_ == lambdastatetype::lm_3) { /* done! */ @@ -84,12 +86,12 @@ namespace xo { rp lm = Lambda::make(name, argl_, body_); p_stack->top_exprstate().on_expr(lm, p_stack, p_emit_expr); - p_stack->top_exprstate().on_semicolon_token(tk, p_stack, p_emit_expr); + p_stack->top_exprstate().on_semicolon_token(tk, p_psm); return; } - exprstate::on_semicolon_token(tk, p_stack, p_emit_expr); + exprstate::on_semicolon_token(tk, p_psm); } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 9ec0a0b8..8f6dff50 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -101,7 +101,7 @@ namespace xo { void paren_xs::on_colon_token(const token_type & tk, - exprstatestack * /*p_stack*/) + parserstatemachine * /*p_psm*/) { constexpr const char * c_self_name = "paren_xs::on_colon"; @@ -110,8 +110,7 @@ namespace xo { void paren_xs::on_semicolon_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { constexpr const char * c_self_name = "paren_xs::on_semicolon"; @@ -120,7 +119,7 @@ namespace xo { void paren_xs::on_singleassign_token(const token_type & tk, - exprstatestack * /*p_stack*/) + parserstatemachine * /*p_psm*/) { constexpr const char * c_self_name = "paren_xs::on_singleassign"; @@ -129,8 +128,7 @@ namespace xo { void paren_xs::on_leftparen_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { constexpr const char * c_self_name = "paren_xs::on_leftparen"; @@ -139,8 +137,7 @@ namespace xo { void paren_xs::on_rightparen_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -155,6 +152,9 @@ namespace xo { if (this->parenxs_type_ == parenexprstatetype::lparen_1) { rp expr = this->gen_expr_; + auto p_stack = p_psm->p_stack_; + auto p_emit_expr = p_psm->p_emit_expr_; + std::unique_ptr self = p_stack->pop_exprstate(); p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); @@ -163,20 +163,14 @@ namespace xo { void paren_xs::on_f64_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); constexpr const char * c_self_name = "paren_xs::on_f64"; - if (!this->admits_f64()) - { - this->illegal_input_error(c_self_name, tk); - } - - assert(false); + this->illegal_input_error(c_self_name, tk); } void diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 431b0cb8..12f62740 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -2,6 +2,7 @@ #include "progress_xs.hpp" #include "expect_expr_xs.hpp" +#include "parserstatemachine.hpp" #include "xo/expression/Apply.hpp" namespace xo { @@ -183,7 +184,7 @@ namespace xo { void progress_xs::on_colon_token(const token_type & tk, - exprstatestack * /*p_stack*/) + parserstatemachine * /*p_psm*/) { constexpr const char * self_name = "progress_xs::on_colon"; @@ -192,14 +193,16 @@ namespace xo { void progress_xs::on_semicolon_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { /* note: implementation parllels .on_rightparen_token() */ constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); + auto p_stack = p_psm->p_stack_; + auto p_emit_expr = p_psm->p_emit_expr_; + rp expr = this->assemble_expr(); std::unique_ptr self = p_stack->pop_exprstate(); @@ -222,12 +225,12 @@ namespace xo { * f. now deliver semicolon; [lparen_1] rejects */ - p_stack->top_exprstate().on_semicolon_token(tk, p_stack, p_emit_expr); + p_stack->top_exprstate().on_semicolon_token(tk, p_psm); } void progress_xs::on_singleassign_token(const token_type & tk, - exprstatestack * /*p_stack*/) + parserstatemachine * /*p_psm*/) { constexpr const char * self_name = "progress_xs::on_singleassign"; @@ -236,8 +239,7 @@ namespace xo { void progress_xs::on_leftparen_token(const token_type & tk, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -249,8 +251,7 @@ namespace xo { void progress_xs::on_rightparen_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { /* note: implementation parallels .on_semicolon_token() */ @@ -260,6 +261,9 @@ namespace xo { constexpr const char * self_name = "progress_xs::on_rightparen"; + auto p_stack = p_psm->p_stack_; + auto p_emit_expr = p_psm->p_emit_expr_; + /* stack may be something like: * * lparen_0 @@ -286,8 +290,7 @@ namespace xo { p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); /* now deliver rightparen */ - p_stack->top_exprstate().on_rightparen_token(tk, p_stack, p_emit_expr); - + p_stack->top_exprstate().on_rightparen_token(tk, p_psm); } namespace { @@ -312,11 +315,12 @@ namespace xo { void progress_xs::on_operator_token(const token_type & tk, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) + parserstatemachine * p_psm) { constexpr const char * c_self_name = "progress_xs::on_operator_token"; + auto p_stack = p_psm->p_stack_; + if (op_type_ == optype::invalid) { this->op_type_ = tk2op(tk.tk_type()); @@ -383,8 +387,7 @@ namespace xo { void progress_xs::on_f64_token(const token_type & tk, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -394,8 +397,7 @@ namespace xo { if (this->op_type_ == optype::invalid) { this->illegal_input_error(self_name, tk); } else { - assert(false); - exprstate::on_f64_token(tk, p_stack, p_emit_expr); + exprstate::on_f64_token(tk, p_psm); } } From 29932f9a3df6001896def291e2d4de14cbb9ba3f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 11:43:20 -0400 Subject: [PATCH 1429/2524] xo-reader: parserstatemachine -> consolidate on_formal_arglist() --- include/xo/reader/exprstate.hpp | 8 ++++++-- include/xo/reader/lambda_xs.hpp | 3 +-- src/reader/expect_formal_arglist_xs.cpp | 4 +--- src/reader/exprstate.cpp | 5 +++-- src/reader/lambda_xs.cpp | 7 ++++--- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index bf93a3f5..3c5e2e6a 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -97,22 +97,26 @@ namespace xo { virtual void on_expr(ref::brw expr, exprstatestack * p_stack, rp * p_emit_expr); + /** update exprstate when expecting a symbol **/ virtual void on_symbol(const std::string & symbol, exprstatestack * p_stack, rp * p_emit_expr); + /** update exprstate when expeccting a typedescr **/ virtual void on_typedescr(TypeDescr td, exprstatestack * p_stack, rp * p_emit_expr); + /** update exprstate when expecting a formal parameter **/ virtual void on_formal(const rp & formal, exprstatestack * p_stack, rp * p_emit_expr); + /** update expression when epecting a formal parameter list **/ virtual void on_formal_arglist(const std::vector> & argl, - exprstatestack * p_stack, - rp * p_emit_expr); + parserstatemachine * p_psm); + /** print human-readable representation on @p os **/ virtual void print(std::ostream & os) const; diff --git a/include/xo/reader/lambda_xs.hpp b/include/xo/reader/lambda_xs.hpp index 59f9f78a..b60f14a6 100644 --- a/include/xo/reader/lambda_xs.hpp +++ b/include/xo/reader/lambda_xs.hpp @@ -45,8 +45,7 @@ namespace xo { virtual void on_lambda_token(const token_type & tk, parserstatemachine * p_psm) override; virtual void on_formal_arglist(const std::vector> & argl, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_expr(ref::brw expr, exprstatestack * p_stack, rp * p_emit_expr) override; diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index 6bb9b099..dc400aeb 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -94,13 +94,11 @@ namespace xo { parserstatemachine * p_psm) { auto p_stack = p_psm->p_stack_; - auto p_emit_expr = p_psm->p_emit_expr_; if (farglxs_type_ == formalarglstatetype::argl_1b) { std::unique_ptr self = p_stack->pop_exprstate(); - p_stack->top_exprstate().on_formal_arglist(this->argl_, - p_stack, p_emit_expr); + p_stack->top_exprstate().on_formal_arglist(this->argl_, p_psm); } else { exprstate::on_rightparen_token(tk, p_psm); } diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index ba58eb79..c9d14c82 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -125,14 +125,15 @@ namespace xo { void exprstate::on_formal_arglist(const std::vector> & argl, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) + parserstatemachine * p_psm) { /* returning type description to something that wants it */ constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); + auto p_stack = p_psm->p_stack_; + log && log(xtag("exstype", p_stack->top_exprstate().exs_type())); diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index 8824ab26..7aaf657d 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -44,15 +44,16 @@ namespace xo { void lambda_xs::on_formal_arglist(const std::vector> & argl, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { + auto p_stack = p_psm->p_stack_; + if (lmxs_type_ == lambdastatetype::lm_1) { this->lmxs_type_ = lambdastatetype::lm_2; this->argl_ = argl; expect_expr_xs::start(p_stack); } else { - exprstate::on_formal_arglist(argl, p_stack, p_emit_expr); + exprstate::on_formal_arglist(argl, p_psm); } } From dbd2f69533b69e6cda48474c8246f383f0b1861a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 11:46:46 -0400 Subject: [PATCH 1430/2524] xo-reader: parserstatemachine -> consolidate on_formal() args --- include/xo/reader/expect_formal_arglist_xs.hpp | 3 +-- include/xo/reader/exprstate.hpp | 3 +-- src/reader/expect_formal_arglist_xs.cpp | 5 ++--- src/reader/expect_formal_xs.cpp | 4 +++- src/reader/exprstate.cpp | 5 +++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/xo/reader/expect_formal_arglist_xs.hpp b/include/xo/reader/expect_formal_arglist_xs.hpp index 7eef773d..5bf75a8f 100644 --- a/include/xo/reader/expect_formal_arglist_xs.hpp +++ b/include/xo/reader/expect_formal_arglist_xs.hpp @@ -58,8 +58,7 @@ namespace xo { virtual void on_leftparen_token(const token_type & tk, parserstatemachine * p_psm) override; virtual void on_formal(const rp & formal, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_comma_token(const token_type & tk, parserstatemachine * p_psm) override; virtual void on_rightparen_token(const token_type & tk, diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 3c5e2e6a..8a7cf0d4 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -110,8 +110,7 @@ namespace xo { /** update exprstate when expecting a formal parameter **/ virtual void on_formal(const rp & formal, - exprstatestack * p_stack, - rp * p_emit_expr); + parserstatemachine * p_psm); /** update expression when epecting a formal parameter list **/ virtual void on_formal_arglist(const std::vector> & argl, diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index dc400aeb..3dcd2c2e 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -64,14 +64,13 @@ namespace xo { void expect_formal_arglist_xs::on_formal(const rp & formal, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { if (farglxs_type_ == formalarglstatetype::argl_1a) { this->farglxs_type_ = formalarglstatetype::argl_1b; this->argl_.push_back(formal); } else { - exprstate::on_formal(formal, p_stack, p_emit_expr); + exprstate::on_formal(formal, p_psm); } } diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp index 3455de23..24484b5b 100644 --- a/src/reader/expect_formal_xs.cpp +++ b/src/reader/expect_formal_xs.cpp @@ -91,7 +91,9 @@ namespace xo { rp var = Variable::make(result_.name(), result_.td()); - p_stack->top_exprstate().on_formal(var, p_stack, p_emit_expr); + parserstatemachine psm(p_stack, p_emit_expr); + + p_stack->top_exprstate().on_formal(var, &psm); } else { exprstate::on_typedescr(td, p_stack, p_emit_expr); } diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index c9d14c82..013f88a5 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -104,14 +104,15 @@ namespace xo { void exprstate::on_formal(const rp & formal, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) + parserstatemachine * p_psm) { /* returning type description to something that wants it */ constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); + auto p_stack = p_psm->p_stack_; + log && log(xtag("exstype", p_stack->top_exprstate().exs_type())); From 370722b086411c96e9c46939edc37dbb87b6f4d5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 11:51:45 -0400 Subject: [PATCH 1431/2524] xo-parser: parserstatemachine -> consolidate on_typedescr() args --- include/xo/reader/define_xs.hpp | 3 +-- include/xo/reader/expect_formal_xs.hpp | 3 +-- include/xo/reader/exprseq_xs.hpp | 3 +-- include/xo/reader/exprstate.hpp | 3 +-- include/xo/reader/paren_xs.hpp | 3 +-- include/xo/reader/progress_xs.hpp | 3 +-- src/reader/define_xs.cpp | 5 ++--- src/reader/expect_formal_xs.cpp | 11 +++++------ src/reader/expect_type_xs.cpp | 3 +-- src/reader/exprseq_xs.cpp | 3 +-- src/reader/exprstate.cpp | 5 +++-- src/reader/paren_xs.cpp | 3 +-- src/reader/progress_xs.cpp | 3 +-- 13 files changed, 20 insertions(+), 31 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 9e616b31..6e3da294 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -81,8 +81,7 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr) override; virtual void on_typedescr(TypeDescr td, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_def_token(const token_type & tk, parserstatemachine * p_psm) override; virtual void on_colon_token(const token_type & tk, diff --git a/include/xo/reader/expect_formal_xs.hpp b/include/xo/reader/expect_formal_xs.hpp index b757f6f5..c1b2e0d4 100644 --- a/include/xo/reader/expect_formal_xs.hpp +++ b/include/xo/reader/expect_formal_xs.hpp @@ -65,8 +65,7 @@ namespace xo { #endif virtual void on_typedescr(TypeDescr td, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void print(std::ostream & os) const override; diff --git a/include/xo/reader/exprseq_xs.hpp b/include/xo/reader/exprseq_xs.hpp index a5576001..2d2cec7b 100644 --- a/include/xo/reader/exprseq_xs.hpp +++ b/include/xo/reader/exprseq_xs.hpp @@ -31,8 +31,7 @@ namespace xo { // ----- victory methods ----- virtual void on_typedescr(TypeDescr td, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_expr(ref::brw expr, exprstatestack * p_stack, rp * p_emit_expr) override; diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 8a7cf0d4..f52cd65e 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -105,8 +105,7 @@ namespace xo { /** update exprstate when expeccting a typedescr **/ virtual void on_typedescr(TypeDescr td, - exprstatestack * p_stack, - rp * p_emit_expr); + parserstatemachine * p_psm); /** update exprstate when expecting a formal parameter **/ virtual void on_formal(const rp & formal, diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index b4225f4e..c250f020 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -41,8 +41,7 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr) override; virtual void on_typedescr(TypeDescr td, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) override; + parserstatemachine * p_psm) override; virtual void on_def_token(const token_type & tk, parserstatemachine * p_psm) override; diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 965da117..92f3df04 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -61,8 +61,7 @@ namespace xo { virtual void on_symbol_token(const token_type & tk, parserstatemachine * p_psm) override; virtual void on_typedescr(TypeDescr td, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) override; + parserstatemachine * p_psm) override; virtual void on_def_token(const token_type & tk, parserstatemachine * p_psm) override; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 0f5561dc..6bc17c13 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -73,8 +73,7 @@ namespace xo { void define_xs::on_typedescr(TypeDescr td, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { if (this->defxs_type_ == defexprstatetype::def_3) { this->defxs_type_ = defexprstatetype::def_4; @@ -85,7 +84,7 @@ namespace xo { return; } else { - exprstate::on_typedescr(td, p_stack, p_emit_expr); + exprstate::on_typedescr(td, p_psm); } } diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp index 24484b5b..99275c02 100644 --- a/src/reader/expect_formal_xs.cpp +++ b/src/reader/expect_formal_xs.cpp @@ -80,9 +80,10 @@ namespace xo { void expect_formal_xs::on_typedescr(TypeDescr td, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { + auto p_stack = p_psm->p_stack_; + if (this->formalxs_type_ == formalstatetype::formal_2) { this->result_.assign_td(td); @@ -91,11 +92,9 @@ namespace xo { rp var = Variable::make(result_.name(), result_.td()); - parserstatemachine psm(p_stack, p_emit_expr); - - p_stack->top_exprstate().on_formal(var, &psm); + p_stack->top_exprstate().on_formal(var, p_psm); } else { - exprstate::on_typedescr(td, p_stack, p_emit_expr); + exprstate::on_typedescr(td, p_psm); } } diff --git a/src/reader/expect_type_xs.cpp b/src/reader/expect_type_xs.cpp index 11ab04ed..90f2932a 100644 --- a/src/reader/expect_type_xs.cpp +++ b/src/reader/expect_type_xs.cpp @@ -33,7 +33,6 @@ namespace xo { const char * c_self_name = "expect_type_xs::on_symbol_token"; auto p_stack = p_psm->p_stack_; - auto p_emit_expr = p_psm->p_emit_expr_; TypeDescr td = nullptr; @@ -59,7 +58,7 @@ namespace xo { } std::unique_ptr self = p_stack->pop_exprstate(); - p_stack->top_exprstate().on_typedescr(td, p_stack, p_emit_expr); + p_stack->top_exprstate().on_typedescr(td, p_psm); } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprseq_xs.cpp b/src/reader/exprseq_xs.cpp index 76722e5b..2d9d7053 100644 --- a/src/reader/exprseq_xs.cpp +++ b/src/reader/exprseq_xs.cpp @@ -51,8 +51,7 @@ namespace xo { void exprseq_xs::on_typedescr(TypeDescr /*td*/, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { /* unreachable - typedescr should never get delivered to exprseq */ assert(false); diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 013f88a5..de77cb52 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -83,14 +83,15 @@ namespace xo { void exprstate::on_typedescr(TypeDescr td, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) + parserstatemachine * p_psm) { /* returning type description to something that wants it */ constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); + auto p_stack = p_psm->p_stack_; + log && log(xtag("exstype", p_stack->top_exprstate().exs_type())); diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 8f6dff50..a536612c 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -92,8 +92,7 @@ namespace xo { void paren_xs::on_typedescr(TypeDescr /*td*/, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { assert(false); return; diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 12f62740..d319bcf1 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -175,8 +175,7 @@ namespace xo { void progress_xs::on_typedescr(TypeDescr /*td*/, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { /* unreachable */ assert(false); From 355f73b2a1e7dcd5c9084ca4c649e0732f00013b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 11:55:11 -0400 Subject: [PATCH 1432/2524] xo-reader: parserstatemachine -> consolidate on_symbol() args --- include/xo/reader/define_xs.hpp | 3 +-- include/xo/reader/expect_formal_xs.hpp | 3 +-- include/xo/reader/exprstate.hpp | 3 +-- include/xo/reader/paren_xs.hpp | 3 +-- src/reader/define_xs.cpp | 5 ++--- src/reader/expect_formal_xs.cpp | 7 ++----- src/reader/expect_symbol_xs.cpp | 4 +--- src/reader/exprstate.cpp | 3 +-- src/reader/paren_xs.cpp | 3 +-- 9 files changed, 11 insertions(+), 23 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 6e3da294..50c76da5 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -78,8 +78,7 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr) override; virtual void on_symbol(const std::string & symbol_name, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_typedescr(TypeDescr td, parserstatemachine * p_psm) override; virtual void on_def_token(const token_type & tk, diff --git a/include/xo/reader/expect_formal_xs.hpp b/include/xo/reader/expect_formal_xs.hpp index c1b2e0d4..64663239 100644 --- a/include/xo/reader/expect_formal_xs.hpp +++ b/include/xo/reader/expect_formal_xs.hpp @@ -50,8 +50,7 @@ namespace xo { static void start(exprstatestack * p_stack); virtual void on_symbol(const std::string & symbol_name, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_colon_token(const token_type & tk, parserstatemachine * p_psm) override; diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index f52cd65e..0f9362ab 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -100,8 +100,7 @@ namespace xo { /** update exprstate when expecting a symbol **/ virtual void on_symbol(const std::string & symbol, - exprstatestack * p_stack, - rp * p_emit_expr); + parserstatemachine * p_psm); /** update exprstate when expeccting a typedescr **/ virtual void on_typedescr(TypeDescr td, diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index c250f020..a45a516a 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -38,8 +38,7 @@ namespace xo { exprstatestack * p_stack, rp * p_emit_expr) override; virtual void on_symbol(const std::string & symbol, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_typedescr(TypeDescr td, parserstatemachine * p_psm) override; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 6bc17c13..30938ce7 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -59,15 +59,14 @@ namespace xo { void define_xs::on_symbol(const std::string & symbol_name, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { if (this->defxs_type_ == defexprstatetype::def_1) { this->defxs_type_ = defexprstatetype::def_2; this->def_expr_->assign_lhs_name(symbol_name); return; } else { - exprstate::on_symbol(symbol_name, p_stack, p_emit_expr); + exprstate::on_symbol(symbol_name, p_psm); } } diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp index 99275c02..94120e79 100644 --- a/src/reader/expect_formal_xs.cpp +++ b/src/reader/expect_formal_xs.cpp @@ -49,16 +49,13 @@ namespace xo { void expect_formal_xs::on_symbol(const std::string & symbol_name, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { if (this->formalxs_type_ == formalstatetype::formal_0) { this->formalxs_type_ = formalstatetype::formal_1; this->result_.assign_name(symbol_name); } else { - exprstate::on_symbol(symbol_name, - p_stack, - p_emit_expr); + exprstate::on_symbol(symbol_name, p_psm); } } diff --git a/src/reader/expect_symbol_xs.cpp b/src/reader/expect_symbol_xs.cpp index 12334e73..bae85927 100644 --- a/src/reader/expect_symbol_xs.cpp +++ b/src/reader/expect_symbol_xs.cpp @@ -28,7 +28,6 @@ namespace xo { parserstatemachine * p_psm) { auto p_stack = p_psm->p_stack_; - auto p_emit_expr = p_psm->p_emit_expr_; /* have to do pop first, before sending symbol to * the o.g. symbol-requester @@ -36,8 +35,7 @@ namespace xo { std::unique_ptr self = p_stack->pop_exprstate(); - p_stack->top_exprstate().on_symbol(tk.text(), - p_stack, p_emit_expr); + p_stack->top_exprstate().on_symbol(tk.text(), p_psm); return; } } /*namespace scm*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index de77cb52..db501154 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -360,8 +360,7 @@ namespace xo { void exprstate::on_symbol(const std::string & symbol_name, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { /* unreachable - derived class that can receive * will override this method diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index a536612c..6f94b37f 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -207,8 +207,7 @@ namespace xo { void paren_xs::on_symbol(const std::string & /*symbol_name*/, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { switch(this->parenxs_type_) { case parenexprstatetype::lparen_0: From bdf75d5620fab76d6a4df17359829939be573074 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 12:17:26 -0400 Subject: [PATCH 1433/2524] xo-reader: parserstatemachine -> consolidate on_expr() args --- include/xo/reader/define_xs.hpp | 3 +-- include/xo/reader/expect_expr_xs.hpp | 3 +-- include/xo/reader/exprseq_xs.hpp | 3 +-- include/xo/reader/exprstate.hpp | 3 +-- include/xo/reader/lambda_xs.hpp | 3 +-- include/xo/reader/paren_xs.hpp | 3 +-- include/xo/reader/progress_xs.hpp | 3 +-- src/reader/define_xs.cpp | 10 +++------- src/reader/expect_expr_xs.cpp | 8 +++----- src/reader/exprseq_xs.cpp | 6 ++++-- src/reader/exprstate.cpp | 3 +-- src/reader/lambda_xs.cpp | 8 +++----- src/reader/paren_xs.cpp | 8 ++++---- src/reader/progress_xs.cpp | 12 ++++-------- 14 files changed, 29 insertions(+), 47 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 50c76da5..ebb7b161 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -75,8 +75,7 @@ namespace xo { defexprstatetype defxs_type() const { return defxs_type_; } virtual void on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_symbol(const std::string & symbol_name, parserstatemachine * p_psm) override; virtual void on_typedescr(TypeDescr td, diff --git a/include/xo/reader/expect_expr_xs.hpp b/include/xo/reader/expect_expr_xs.hpp index 3808e7bc..90477c72 100644 --- a/include/xo/reader/expect_expr_xs.hpp +++ b/include/xo/reader/expect_expr_xs.hpp @@ -33,8 +33,7 @@ namespace xo { /** update exprstate in response to a successfully-parsed subexpression **/ virtual void on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; private: static std::unique_ptr make(); diff --git a/include/xo/reader/exprseq_xs.hpp b/include/xo/reader/exprseq_xs.hpp index 2d2cec7b..e1808a15 100644 --- a/include/xo/reader/exprseq_xs.hpp +++ b/include/xo/reader/exprseq_xs.hpp @@ -33,8 +33,7 @@ namespace xo { virtual void on_typedescr(TypeDescr td, parserstatemachine * p_psm) override; virtual void on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; private: static std::unique_ptr make(); diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 0f9362ab..d2c58d67 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -95,8 +95,7 @@ namespace xo { /** update exprstate in response to a successfully-parsed subexpression **/ virtual void on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * p_emit_expr); + parserstatemachine * p_psm); /** update exprstate when expecting a symbol **/ virtual void on_symbol(const std::string & symbol, diff --git a/include/xo/reader/lambda_xs.hpp b/include/xo/reader/lambda_xs.hpp index b60f14a6..5ad0adfb 100644 --- a/include/xo/reader/lambda_xs.hpp +++ b/include/xo/reader/lambda_xs.hpp @@ -47,8 +47,7 @@ namespace xo { virtual void on_formal_arglist(const std::vector> & argl, parserstatemachine * p_psm) override; virtual void on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_semicolon_token(const token_type & tk, parserstatemachine * p_psm) override; diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index a45a516a..0fd6bf86 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -35,8 +35,7 @@ namespace xo { bool admits_rightparen() const; virtual void on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_symbol(const std::string & symbol, parserstatemachine * p_psm) override; virtual void on_typedescr(TypeDescr td, diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 92f3df04..6436b117 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -56,8 +56,7 @@ namespace xo { bool admits_f64() const; virtual void on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * p_emit_expr) override; + parserstatemachine * p_psm) override; virtual void on_symbol_token(const token_type & tk, parserstatemachine * p_psm) override; virtual void on_typedescr(TypeDescr td, diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 30938ce7..58006125 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -30,8 +30,7 @@ namespace xo { void define_xs::on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { if (this->defxs_type_ == defexprstatetype::def_5) { /* have all the ingredients to create an expression @@ -54,7 +53,7 @@ namespace xo { return; } - exprstate::on_expr(expr, p_stack, p_emit_expr); + exprstate::on_expr(expr, p_psm); } void @@ -133,7 +132,6 @@ namespace xo { scope log(XO_DEBUG(c_debug_flag)); auto p_stack = p_psm->p_stack_; - auto p_emit_expr = p_psm->p_emit_expr_; //constexpr const char * self_name = "exprstate::on_semicolon"; @@ -142,9 +140,7 @@ namespace xo { std::unique_ptr self = p_stack->pop_exprstate(); - p_stack->top_exprstate().on_expr(expr, - p_stack, - p_emit_expr); + p_stack->top_exprstate().on_expr(expr, p_psm); } else { exprstate::on_semicolon_token(tk, p_psm); } diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index 123f52da..74afec19 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -118,8 +118,7 @@ namespace xo { void expect_expr_xs::on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -127,12 +126,11 @@ namespace xo { log && log(xtag("exstype", this->exs_type_), xtag("expr", expr)); + auto p_stack = p_psm->p_stack_; std::unique_ptr self = p_stack->pop_exprstate(); - p_stack->top_exprstate().on_expr(expr, - p_stack, - p_emit_expr); + p_stack->top_exprstate().on_expr(expr, p_psm); } /*on_expr*/ } /*namespace scm*/ diff --git a/src/reader/exprseq_xs.cpp b/src/reader/exprseq_xs.cpp index 2d9d7053..44f28624 100644 --- a/src/reader/exprseq_xs.cpp +++ b/src/reader/exprseq_xs.cpp @@ -3,6 +3,7 @@ #include "exprseq_xs.hpp" #include "define_xs.hpp" #include "expect_symbol_xs.hpp" +#include "parserstatemachine.hpp" namespace xo { namespace scm { @@ -60,8 +61,7 @@ namespace xo { void exprseq_xs::on_expr(ref::brw expr, - exprstatestack * /*p_stack*/, - rp * p_emit_expr) + parserstatemachine * p_psm) { /* toplevel expression sequence accepts an * arbitrary number of expressions. @@ -69,6 +69,8 @@ namespace xo { * parser::include_token() returns */ + auto p_emit_expr = p_psm->p_emit_expr_; + *p_emit_expr = expr.promote(); } /*on_expr*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index db501154..1b9644b6 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -346,8 +346,7 @@ namespace xo { void exprstate::on_expr(ref::brw expr, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index 7aaf657d..fbde492a 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -59,14 +59,13 @@ namespace xo { void lambda_xs::on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * p_emit_expr) + parserstatemachine * p_psm) { if (lmxs_type_ == lambdastatetype::lm_2) { this->lmxs_type_ = lambdastatetype::lm_3; this->body_ = expr.promote(); } else { - exprstate::on_expr(expr, p_stack, p_emit_expr); + exprstate::on_expr(expr, p_psm); } } @@ -75,7 +74,6 @@ namespace xo { parserstatemachine * p_psm) { auto p_stack = p_psm->p_stack_; - auto p_emit_expr = p_psm->p_emit_expr_; if (lmxs_type_ == lambdastatetype::lm_3) { /* done! */ @@ -86,7 +84,7 @@ namespace xo { rp lm = Lambda::make(name, argl_, body_); - p_stack->top_exprstate().on_expr(lm, p_stack, p_emit_expr); + p_stack->top_exprstate().on_expr(lm, p_psm); p_stack->top_exprstate().on_semicolon_token(tk, p_psm); return; diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 6f94b37f..b9dde8f4 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -152,11 +152,10 @@ namespace xo { rp expr = this->gen_expr_; auto p_stack = p_psm->p_stack_; - auto p_emit_expr = p_psm->p_emit_expr_; std::unique_ptr self = p_stack->pop_exprstate(); - p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); + p_stack->top_exprstate().on_expr(expr, p_psm); } } @@ -174,8 +173,7 @@ namespace xo { void paren_xs::on_expr(ref::brw expr, - exprstatestack * p_stack, - rp * /*p_emit_expr*/) + parserstatemachine * p_psm) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -186,6 +184,8 @@ namespace xo { switch (this->parenxs_type_) { case parenexprstatetype::lparen_0: { this->parenxs_type_ = parenexprstatetype::lparen_1; /* wants on_rightparen */ + auto p_stack = p_psm->p_stack_; + progress_xs::start(expr.promote(), p_stack); return; diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index d319bcf1..6b474db3 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -134,8 +134,7 @@ namespace xo { void progress_xs::on_expr(ref::brw expr, - exprstatestack * /*p_stack*/, - rp * /*p_emit_expr*/) + parserstatemachine * /*p_psm*/) { /* note: previous token probably an operator, * handled from progress_xs::on_operator_token(), @@ -200,15 +199,13 @@ namespace xo { scope log(XO_DEBUG(c_debug_flag)); auto p_stack = p_psm->p_stack_; - auto p_emit_expr = p_psm->p_emit_expr_; rp expr = this->assemble_expr(); std::unique_ptr self = p_stack->pop_exprstate(); - p_stack->top_exprstate().on_expr(expr, - p_stack, - p_emit_expr); + p_stack->top_exprstate().on_expr(expr, p_psm); + /* control here on input like: * (1.234; * @@ -261,7 +258,6 @@ namespace xo { constexpr const char * self_name = "progress_xs::on_rightparen"; auto p_stack = p_psm->p_stack_; - auto p_emit_expr = p_psm->p_emit_expr_; /* stack may be something like: * @@ -286,7 +282,7 @@ namespace xo { log && log(xtag("stack", p_stack)); - p_stack->top_exprstate().on_expr(expr, p_stack, p_emit_expr); + p_stack->top_exprstate().on_expr(expr, p_psm); /* now deliver rightparen */ p_stack->top_exprstate().on_rightparen_token(tk, p_psm); From bcb2af4a56d394ee7f04fe8d4df6c515aa9c3273 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 13:48:47 -0400 Subject: [PATCH 1434/2524] xo-reader: split: exprstatestack to own .*pp files --- include/xo/reader/exprstate.hpp | 53 ------------------ include/xo/reader/exprstatestack.hpp | 66 +++++++++++++++++++++++ include/xo/reader/parser.hpp | 2 +- src/reader/CMakeLists.txt | 1 + src/reader/define_xs.cpp | 1 + src/reader/expect_expr_xs.cpp | 1 + src/reader/expect_formal_arglist_xs.cpp | 3 +- src/reader/expect_formal_xs.cpp | 1 + src/reader/expect_symbol_xs.cpp | 1 + src/reader/expect_type_xs.cpp | 1 + src/reader/exprseq_xs.cpp | 3 +- src/reader/exprstate.cpp | 62 +-------------------- src/reader/exprstatestack.cpp | 71 +++++++++++++++++++++++++ src/reader/lambda_xs.cpp | 1 + src/reader/paren_xs.cpp | 1 + src/reader/progress_xs.cpp | 1 + 16 files changed, 152 insertions(+), 117 deletions(-) create mode 100644 include/xo/reader/exprstatestack.hpp create mode 100644 src/reader/exprstatestack.cpp diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index d2c58d67..3e79dee4 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -171,59 +171,6 @@ namespace xo { x.print(os); return os; } - - // ----- exprstatestack ----- - - /** @class exprstatestack - * @brief A stack of exprstate objects - **/ - class exprstatestack { - public: - exprstatestack() {} - - bool empty() const { return stack_.empty(); } - std::size_t size() const { return stack_.size(); } - - exprstate & top_exprstate(); - void push_exprstate(std::unique_ptr exs); - std::unique_ptr pop_exprstate(); - - /** relative to top-of-stack. - * 0 -> top (last in), z-1 -> bottom (first in) - **/ - std::unique_ptr & operator[](std::size_t i) { - std::size_t z = stack_.size(); - - assert(i < z); - - return stack_[z - i - 1]; - } - - const std::unique_ptr & operator[](std::size_t i) const { - std::size_t z = stack_.size(); - - assert(i < z); - - return stack_[z - i - 1]; - } - - void print (std::ostream & os) const; - - private: - std::vector> stack_; - }; - - inline std::ostream & - operator<< (std::ostream & os, const exprstatestack & x) { - x.print(os); - return os; - } - - inline std::ostream & - operator<< (std::ostream & os, const exprstatestack * x) { - x->print(os); - return os; - } } /*namespace scm*/ } /*namespace xo*/ diff --git a/include/xo/reader/exprstatestack.hpp b/include/xo/reader/exprstatestack.hpp new file mode 100644 index 00000000..c5ee0894 --- /dev/null +++ b/include/xo/reader/exprstatestack.hpp @@ -0,0 +1,66 @@ +/* file exprstatestack.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "exprstate.hpp" + +namespace xo { + namespace scm { + /** @class exprstatestack + * @brief A stack of exprstate objects + **/ + class exprstatestack { + public: + exprstatestack() {} + + bool empty() const { return stack_.empty(); } + std::size_t size() const { return stack_.size(); } + + exprstate & top_exprstate(); + void push_exprstate(std::unique_ptr exs); + std::unique_ptr pop_exprstate(); + + /** relative to top-of-stack. + * 0 -> top (last in), z-1 -> bottom (first in) + **/ + std::unique_ptr & operator[](std::size_t i) { + std::size_t z = stack_.size(); + + assert(i < z); + + return stack_[z - i - 1]; + } + + const std::unique_ptr & operator[](std::size_t i) const { + std::size_t z = stack_.size(); + + assert(i < z); + + return stack_[z - i - 1]; + } + + void print (std::ostream & os) const; + + private: + std::vector> stack_; + }; + + inline std::ostream & + operator<< (std::ostream & os, const exprstatestack & x) { + x.print(os); + return os; + } + + inline std::ostream & + operator<< (std::ostream & os, const exprstatestack * x) { + x->print(os); + return os; + } + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end exprstatestack.hpp */ diff --git a/include/xo/reader/parser.hpp b/include/xo/reader/parser.hpp index 3211fd0c..dae3091f 100644 --- a/include/xo/reader/parser.hpp +++ b/include/xo/reader/parser.hpp @@ -5,7 +5,7 @@ #pragma once -#include "exprstate.hpp" +#include "exprstatestack.hpp" #include namespace xo { diff --git a/src/reader/CMakeLists.txt b/src/reader/CMakeLists.txt index 32a4cd5a..0a2f7704 100644 --- a/src/reader/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -5,6 +5,7 @@ set(SELF_SRCS parser.cpp reader.cpp exprstate.cpp + exprstatestack.cpp define_xs.cpp progress_xs.cpp paren_xs.cpp diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 58006125..f0e7924f 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -1,6 +1,7 @@ /* @file define_xs.cpp */ #include "define_xs.hpp" +#include "exprstatestack.hpp" #include "parserstatemachine.hpp" #include "expect_symbol_xs.hpp" #include "expect_expr_xs.hpp" diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index 74afec19..3c5001ae 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -5,6 +5,7 @@ #include "expect_expr_xs.hpp" #include "parserstatemachine.hpp" +#include "exprstatestack.hpp" #include "lambda_xs.hpp" #include "paren_xs.hpp" #include "progress_xs.hpp" diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index 3dcd2c2e..1eef0563 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -4,9 +4,10 @@ */ #include "expect_formal_arglist_xs.hpp" +#include "parserstatemachine.hpp" +#include "exprstatestack.hpp" #include "expect_formal_xs.hpp" #include "expect_symbol_xs.hpp" -#include "parserstatemachine.hpp" #include "xo/expression/Variable.hpp" #include "xo/indentlog/print/vector.hpp" diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp index 94120e79..8bdcf6b1 100644 --- a/src/reader/expect_formal_xs.cpp +++ b/src/reader/expect_formal_xs.cpp @@ -7,6 +7,7 @@ #include "expect_symbol_xs.hpp" #include "expect_type_xs.hpp" #include "parserstatemachine.hpp" +#include "exprstatestack.hpp" #include "xo/expression/Variable.hpp" namespace xo { diff --git a/src/reader/expect_symbol_xs.cpp b/src/reader/expect_symbol_xs.cpp index bae85927..591f4ca8 100644 --- a/src/reader/expect_symbol_xs.cpp +++ b/src/reader/expect_symbol_xs.cpp @@ -5,6 +5,7 @@ #include "expect_symbol_xs.hpp" #include "parserstatemachine.hpp" +#include "exprstatestack.hpp" namespace xo { namespace scm { diff --git a/src/reader/expect_type_xs.cpp b/src/reader/expect_type_xs.cpp index 90f2932a..97365062 100644 --- a/src/reader/expect_type_xs.cpp +++ b/src/reader/expect_type_xs.cpp @@ -5,6 +5,7 @@ #include "expect_type_xs.hpp" #include "parserstatemachine.hpp" +#include "exprstatestack.hpp" #include "xo/reflect/Reflect.hpp" namespace xo { diff --git a/src/reader/exprseq_xs.cpp b/src/reader/exprseq_xs.cpp index 44f28624..0db1a394 100644 --- a/src/reader/exprseq_xs.cpp +++ b/src/reader/exprseq_xs.cpp @@ -1,9 +1,10 @@ /* @file exprseq_xs.cpp */ #include "exprseq_xs.hpp" +#include "parserstatemachine.hpp" +#include "exprstatestack.hpp" #include "define_xs.hpp" #include "expect_symbol_xs.hpp" -#include "parserstatemachine.hpp" namespace xo { namespace scm { diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 1b9644b6..9a2917e0 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -1,6 +1,7 @@ /* @file exprstate.cpp */ #include "exprstate.hpp" +#include "exprstatestack.hpp" #include "parserstatemachine.hpp" //#include "formal_arg.hpp" #include "xo/expression/Variable.hpp" @@ -390,67 +391,6 @@ namespace xo { xtag("token", tk), xtag("state", *this))); } - - // ----- exprstatestack ----- - - exprstate & - exprstatestack::top_exprstate() { - std::size_t z = stack_.size(); - - if (z == 0) { - throw std::runtime_error - ("parser::top_exprstate: unexpected empty stack"); - } - - return *(stack_[z-1]); - } - - void - exprstatestack::push_exprstate(std::unique_ptr exs) { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag), - xtag("exs", *exs)); - - std::size_t z = stack_.size(); - - stack_.resize(z+1); - - stack_[z] = std::move(exs); - } - - std::unique_ptr - exprstatestack::pop_exprstate() { - constexpr bool c_debug_flag = true; - scope log(XO_DEBUG(c_debug_flag), - xtag("top.exstype", top_exprstate().exs_type())); - - std::size_t z = stack_.size(); - - if (z > 0) { - std::unique_ptr top = std::move(stack_[z-1]); - - stack_.resize(z-1); - - return top; - } else { - return nullptr; - } - } - - void - exprstatestack::print(std::ostream & os) const { - os << "" << std::endl; - } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstatestack.cpp b/src/reader/exprstatestack.cpp new file mode 100644 index 00000000..07ca85bb --- /dev/null +++ b/src/reader/exprstatestack.cpp @@ -0,0 +1,71 @@ +/* file exprstatestack.cpp + * + * author: Roland Conybeare + */ + +#include "exprstatestack.hpp" + +namespace xo { + namespace scm { + exprstate & + exprstatestack::top_exprstate() { + std::size_t z = stack_.size(); + + if (z == 0) { + throw std::runtime_error + ("parser::top_exprstate: unexpected empty stack"); + } + + return *(stack_[z-1]); + } + + void + exprstatestack::push_exprstate(std::unique_ptr exs) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag), + xtag("exs", *exs)); + + std::size_t z = stack_.size(); + + stack_.resize(z+1); + + stack_[z] = std::move(exs); + } + + std::unique_ptr + exprstatestack::pop_exprstate() { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag), + xtag("top.exstype", top_exprstate().exs_type())); + + std::size_t z = stack_.size(); + + if (z > 0) { + std::unique_ptr top = std::move(stack_[z-1]); + + stack_.resize(z-1); + + return top; + } else { + return nullptr; + } + } + + void + exprstatestack::print(std::ostream & os) const { + os << "" << std::endl; + } + } /*namespace scm*/ +} /*namespace xo*/ + +/* end exprstatestack.cpp */ diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index fbde492a..90270b07 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -2,6 +2,7 @@ #include "lambda_xs.hpp" #include "parserstatemachine.hpp" +#include "exprstatestack.hpp" #include "expect_formal_arglist_xs.hpp" #include "expect_expr_xs.hpp" #include "xo/expression/Lambda.hpp" diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index b9dde8f4..96318e4c 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -2,6 +2,7 @@ #include "paren_xs.hpp" #include "parserstatemachine.hpp" +#include "exprstatestack.hpp" #include "progress_xs.hpp" #include "expect_expr_xs.hpp" diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 6b474db3..d1bae5c3 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -1,6 +1,7 @@ /* @file progress_xs.cpp */ #include "progress_xs.hpp" +#include "exprstatestack.hpp" #include "expect_expr_xs.hpp" #include "parserstatemachine.hpp" #include "xo/expression/Apply.hpp" From 4232da4ef2c9fadf86d0cd5ab25d3c280320bef0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 13:54:58 -0400 Subject: [PATCH 1435/2524] xo-reader: streamline: + parserstatemachine::pop_exprstate() + use --- include/xo/reader/parserstatemachine.hpp | 2 ++ src/reader/CMakeLists.txt | 1 + src/reader/define_xs.cpp | 2 +- src/reader/expect_expr_xs.cpp | 4 ++-- src/reader/expect_formal_arglist_xs.cpp | 2 +- src/reader/expect_formal_xs.cpp | 2 +- src/reader/expect_symbol_xs.cpp | 2 +- src/reader/expect_type_xs.cpp | 2 +- src/reader/lambda_xs.cpp | 2 +- src/reader/paren_xs.cpp | 2 +- src/reader/parserstatemachine.cpp | 19 +++++++++++++++++++ src/reader/progress_xs.cpp | 10 +++++----- 12 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 src/reader/parserstatemachine.cpp diff --git a/include/xo/reader/parserstatemachine.hpp b/include/xo/reader/parserstatemachine.hpp index 55b285ee..060ec841 100644 --- a/include/xo/reader/parserstatemachine.hpp +++ b/include/xo/reader/parserstatemachine.hpp @@ -25,6 +25,8 @@ namespace xo { rp * p_emit_expr) : p_stack_{p_stack}, p_emit_expr_{p_emit_expr} {} + std::unique_ptr pop_exprstate(); + public: exprstatestack * p_stack_; rp * p_emit_expr_; diff --git a/src/reader/CMakeLists.txt b/src/reader/CMakeLists.txt index 0a2f7704..5edbb2e4 100644 --- a/src/reader/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -3,6 +3,7 @@ set(SELF_LIB xo_reader) set(SELF_SRCS parser.cpp + parserstatemachine.cpp reader.cpp exprstate.cpp exprstatestack.cpp diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index f0e7924f..a4d9f1b6 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -139,7 +139,7 @@ namespace xo { if (this->defxs_type_ == defexprstatetype::def_6) { rp expr = this->def_expr_; - std::unique_ptr self = p_stack->pop_exprstate(); + std::unique_ptr self = p_psm->pop_exprstate(); p_stack->top_exprstate().on_expr(expr, p_psm); } else { diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index 3c5001ae..538bd45c 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -91,7 +91,7 @@ namespace xo { #endif #ifdef LATER - p_stack->pop_exprstate(); + p_psm->pop_exprstate(); p_stack->top_exprstate().on_symbol(tk.text(), p_stack, p_emit_expr); #endif @@ -129,7 +129,7 @@ namespace xo { auto p_stack = p_psm->p_stack_; - std::unique_ptr self = p_stack->pop_exprstate(); + std::unique_ptr self = p_psm->pop_exprstate(); p_stack->top_exprstate().on_expr(expr, p_psm); } /*on_expr*/ diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index 1eef0563..1dc27429 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -96,7 +96,7 @@ namespace xo { auto p_stack = p_psm->p_stack_; if (farglxs_type_ == formalarglstatetype::argl_1b) { - std::unique_ptr self = p_stack->pop_exprstate(); + std::unique_ptr self = p_psm->pop_exprstate(); p_stack->top_exprstate().on_formal_arglist(this->argl_, p_psm); } else { diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp index 8bdcf6b1..2cb2e9be 100644 --- a/src/reader/expect_formal_xs.cpp +++ b/src/reader/expect_formal_xs.cpp @@ -85,7 +85,7 @@ namespace xo { if (this->formalxs_type_ == formalstatetype::formal_2) { this->result_.assign_td(td); - std::unique_ptr self = p_stack->pop_exprstate(); + std::unique_ptr self = p_psm->pop_exprstate(); rp var = Variable::make(result_.name(), result_.td()); diff --git a/src/reader/expect_symbol_xs.cpp b/src/reader/expect_symbol_xs.cpp index 591f4ca8..77219efb 100644 --- a/src/reader/expect_symbol_xs.cpp +++ b/src/reader/expect_symbol_xs.cpp @@ -33,7 +33,7 @@ namespace xo { /* have to do pop first, before sending symbol to * the o.g. symbol-requester */ - std::unique_ptr self = p_stack->pop_exprstate(); + std::unique_ptr self = p_psm->pop_exprstate(); p_stack->top_exprstate().on_symbol(tk.text(), p_psm); diff --git a/src/reader/expect_type_xs.cpp b/src/reader/expect_type_xs.cpp index 97365062..9bbf4e18 100644 --- a/src/reader/expect_type_xs.cpp +++ b/src/reader/expect_type_xs.cpp @@ -58,7 +58,7 @@ namespace xo { xtag("typename", tk.text()))); } - std::unique_ptr self = p_stack->pop_exprstate(); + std::unique_ptr self = p_psm->pop_exprstate(); p_stack->top_exprstate().on_typedescr(td, p_psm); } } /*namespace scm*/ diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index 90270b07..80aac681 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -79,7 +79,7 @@ namespace xo { if (lmxs_type_ == lambdastatetype::lm_3) { /* done! */ - std::unique_ptr self = p_stack->pop_exprstate(); + std::unique_ptr self = p_psm->pop_exprstate(); std::string name = "fixmename"; diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 96318e4c..c39a89cb 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -154,7 +154,7 @@ namespace xo { auto p_stack = p_psm->p_stack_; - std::unique_ptr self = p_stack->pop_exprstate(); + std::unique_ptr self = p_psm->pop_exprstate(); p_stack->top_exprstate().on_expr(expr, p_psm); } diff --git a/src/reader/parserstatemachine.cpp b/src/reader/parserstatemachine.cpp new file mode 100644 index 00000000..6d47b909 --- /dev/null +++ b/src/reader/parserstatemachine.cpp @@ -0,0 +1,19 @@ +/* file parserstatemachine.cpp + * + * author: Roland Conybeare + */ + +#include "parserstatemachine.hpp" +#include "exprstatestack.hpp" + +namespace xo { + namespace scm { + std::unique_ptr + parserstatemachine::pop_exprstate() { + return p_stack_->pop_exprstate(); + } + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end parserstatemachine.cpp */ diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index d1bae5c3..3431e689 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -154,7 +154,7 @@ namespace xo { assert(result.get()); /* this expression complete.. */ - std::unique_ptr self = p_stack->pop_exprstate(); + std::unique_ptr self = p_psm->pop_exprstate(); /* ..but more operators could follow, so don't commit yet */ p_stack->push_exprstate(progress_xs::make(result)); @@ -203,7 +203,7 @@ namespace xo { rp expr = this->assemble_expr(); - std::unique_ptr self = p_stack->pop_exprstate(); + std::unique_ptr self = p_psm->pop_exprstate(); p_stack->top_exprstate().on_expr(expr, p_psm); @@ -274,7 +274,7 @@ namespace xo { /* right paren confirms stack expression */ rp expr = this->assemble_expr(); - std::unique_ptr self = p_stack->pop_exprstate(); + std::unique_ptr self = p_psm->pop_exprstate(); if (p_stack->empty()) { throw std::runtime_error(tostr(self_name, @@ -344,7 +344,7 @@ namespace xo { auto expr = this->assemble_expr(); /* 2. remove from stack */ - std::unique_ptr self = p_stack->pop_exprstate(); + std::unique_ptr self = p_psm->pop_exprstate(); /* 3. replace with new progress_xs: */ progress_xs::start(expr, op2, p_stack); @@ -365,7 +365,7 @@ namespace xo { * 4. expect_rhs_expression */ - std::unique_ptr self = p_stack->pop_exprstate(); + std::unique_ptr self = p_psm->pop_exprstate(); /* 1. replace with nested incomplete infix exprs */ progress_xs::start(lhs_, op_type_, p_stack); From b988bc67903fbb7e30dc572531b1a7431f851a30 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 15:55:36 -0400 Subject: [PATCH 1436/2524] xo-reader: parsestatemachine.top_exprstate() + use to simplify --- include/xo/reader/parserstatemachine.hpp | 1 + src/reader/define_xs.cpp | 6 ++---- src/reader/expect_expr_xs.cpp | 8 +++----- src/reader/expect_formal_arglist_xs.cpp | 4 +--- src/reader/expect_formal_xs.cpp | 4 +--- src/reader/expect_symbol_xs.cpp | 4 +--- src/reader/expect_type_xs.cpp | 4 +--- src/reader/exprstate.cpp | 16 ++++------------ src/reader/lambda_xs.cpp | 6 ++---- src/reader/paren_xs.cpp | 8 ++------ src/reader/progress_xs.cpp | 10 ++++------ 11 files changed, 22 insertions(+), 49 deletions(-) diff --git a/include/xo/reader/parserstatemachine.hpp b/include/xo/reader/parserstatemachine.hpp index 060ec841..c5222194 100644 --- a/include/xo/reader/parserstatemachine.hpp +++ b/include/xo/reader/parserstatemachine.hpp @@ -26,6 +26,7 @@ namespace xo { : p_stack_{p_stack}, p_emit_expr_{p_emit_expr} {} std::unique_ptr pop_exprstate(); + exprstate & top_exprstate(); public: exprstatestack * p_stack_; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index a4d9f1b6..4672bc96 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -20,7 +20,7 @@ namespace xo { auto p_stack = p_psm->p_stack_; p_stack->push_exprstate(define_xs::make()); - p_stack->top_exprstate().on_def_token(token_type::def(), p_psm); + p_psm->top_exprstate().on_def_token(token_type::def(), p_psm); } define_xs::define_xs(rp def_expr) @@ -132,8 +132,6 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - auto p_stack = p_psm->p_stack_; - //constexpr const char * self_name = "exprstate::on_semicolon"; if (this->defxs_type_ == defexprstatetype::def_6) { @@ -141,7 +139,7 @@ namespace xo { std::unique_ptr self = p_psm->pop_exprstate(); - p_stack->top_exprstate().on_expr(expr, p_psm); + p_psm->top_exprstate().on_expr(expr, p_psm); } else { exprstate::on_semicolon_token(tk, p_psm); } diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index 538bd45c..bdd66b1a 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -46,7 +46,7 @@ namespace xo { /* push lparen_0 to remember to look for subsequent rightparen. */ lambda_xs::start(p_stack, p_emit_expr); - //p_stack->top_exprstate().on_lambda_token(tk, p_stack, p_emit_expr); + //p_psm->top_exprstate().on_lambda_token(tk, p_stack, p_emit_expr); //p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); } @@ -92,7 +92,7 @@ namespace xo { #ifdef LATER p_psm->pop_exprstate(); - p_stack->top_exprstate().on_symbol(tk.text(), + p_psm->top_exprstate().on_symbol(tk.text(), p_stack, p_emit_expr); #endif return; @@ -127,11 +127,9 @@ namespace xo { log && log(xtag("exstype", this->exs_type_), xtag("expr", expr)); - auto p_stack = p_psm->p_stack_; - std::unique_ptr self = p_psm->pop_exprstate(); - p_stack->top_exprstate().on_expr(expr, p_psm); + p_psm->top_exprstate().on_expr(expr, p_psm); } /*on_expr*/ } /*namespace scm*/ diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index 1dc27429..2f9f0fce 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -93,12 +93,10 @@ namespace xo { expect_formal_arglist_xs::on_rightparen_token(const token_type & tk, parserstatemachine * p_psm) { - auto p_stack = p_psm->p_stack_; - if (farglxs_type_ == formalarglstatetype::argl_1b) { std::unique_ptr self = p_psm->pop_exprstate(); - p_stack->top_exprstate().on_formal_arglist(this->argl_, p_psm); + p_psm->top_exprstate().on_formal_arglist(this->argl_, p_psm); } else { exprstate::on_rightparen_token(tk, p_psm); } diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp index 2cb2e9be..7bd8773d 100644 --- a/src/reader/expect_formal_xs.cpp +++ b/src/reader/expect_formal_xs.cpp @@ -80,8 +80,6 @@ namespace xo { expect_formal_xs::on_typedescr(TypeDescr td, parserstatemachine * p_psm) { - auto p_stack = p_psm->p_stack_; - if (this->formalxs_type_ == formalstatetype::formal_2) { this->result_.assign_td(td); @@ -90,7 +88,7 @@ namespace xo { rp var = Variable::make(result_.name(), result_.td()); - p_stack->top_exprstate().on_formal(var, p_psm); + p_psm->top_exprstate().on_formal(var, p_psm); } else { exprstate::on_typedescr(td, p_psm); } diff --git a/src/reader/expect_symbol_xs.cpp b/src/reader/expect_symbol_xs.cpp index 77219efb..3c8bfd2d 100644 --- a/src/reader/expect_symbol_xs.cpp +++ b/src/reader/expect_symbol_xs.cpp @@ -28,15 +28,13 @@ namespace xo { expect_symbol_xs::on_symbol_token(const token_type & tk, parserstatemachine * p_psm) { - auto p_stack = p_psm->p_stack_; - /* have to do pop first, before sending symbol to * the o.g. symbol-requester */ std::unique_ptr self = p_psm->pop_exprstate(); - p_stack->top_exprstate().on_symbol(tk.text(), p_psm); + p_psm->top_exprstate().on_symbol(tk.text(), p_psm); return; } } /*namespace scm*/ diff --git a/src/reader/expect_type_xs.cpp b/src/reader/expect_type_xs.cpp index 9bbf4e18..f8efd265 100644 --- a/src/reader/expect_type_xs.cpp +++ b/src/reader/expect_type_xs.cpp @@ -33,8 +33,6 @@ namespace xo { { const char * c_self_name = "expect_type_xs::on_symbol_token"; - auto p_stack = p_psm->p_stack_; - TypeDescr td = nullptr; /* TODO: replace with typetable lookup */ @@ -59,7 +57,7 @@ namespace xo { } std::unique_ptr self = p_psm->pop_exprstate(); - p_stack->top_exprstate().on_typedescr(td, p_psm); + p_psm->top_exprstate().on_typedescr(td, p_psm); } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 9a2917e0..9721ede2 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -73,9 +73,7 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - auto p_stack = p_psm->p_stack_; - - log && log(xtag("exstype", p_stack->top_exprstate().exs_type())); + log && log(xtag("exstype", p_psm->top_exprstate().exs_type())); constexpr const char * c_self_name = "exprstate::on_symbol_token"; @@ -91,10 +89,8 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - auto p_stack = p_psm->p_stack_; - log && log(xtag("exstype", - p_stack->top_exprstate().exs_type())); + p_psm->top_exprstate().exs_type())); constexpr const char * c_self_name = "exprstate::on_typedescr"; @@ -113,10 +109,8 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - auto p_stack = p_psm->p_stack_; - log && log(xtag("exstype", - p_stack->top_exprstate().exs_type())); + p_psm->top_exprstate().exs_type())); constexpr const char * c_self_name = "exprstate::on_formal"; @@ -135,10 +129,8 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - auto p_stack = p_psm->p_stack_; - log && log(xtag("exstype", - p_stack->top_exprstate().exs_type())); + p_psm->top_exprstate().exs_type())); constexpr const char * c_self_name = "exprstate::on_formal_arglist"; diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index 80aac681..1ad6f401 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -74,8 +74,6 @@ namespace xo { lambda_xs::on_semicolon_token(const token_type & tk, parserstatemachine * p_psm) { - auto p_stack = p_psm->p_stack_; - if (lmxs_type_ == lambdastatetype::lm_3) { /* done! */ @@ -85,8 +83,8 @@ namespace xo { rp lm = Lambda::make(name, argl_, body_); - p_stack->top_exprstate().on_expr(lm, p_psm); - p_stack->top_exprstate().on_semicolon_token(tk, p_psm); + p_psm->top_exprstate().on_expr(lm, p_psm); + p_psm->top_exprstate().on_semicolon_token(tk, p_psm); return; } diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index c39a89cb..d1d9535c 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -80,9 +80,7 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - auto p_stack = p_psm->p_stack_; - - log && log(xtag("exstype", p_stack->top_exprstate().exs_type())); + log && log(xtag("exstype", p_psm->top_exprstate().exs_type())); //constexpr const char * self_name = "paren_xs::on_symbol"; @@ -152,11 +150,9 @@ namespace xo { if (this->parenxs_type_ == parenexprstatetype::lparen_1) { rp expr = this->gen_expr_; - auto p_stack = p_psm->p_stack_; - std::unique_ptr self = p_psm->pop_exprstate(); - p_stack->top_exprstate().on_expr(expr, p_psm); + p_psm->top_exprstate().on_expr(expr, p_psm); } } diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 3431e689..6f62f853 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -199,13 +199,11 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - auto p_stack = p_psm->p_stack_; - rp expr = this->assemble_expr(); std::unique_ptr self = p_psm->pop_exprstate(); - p_stack->top_exprstate().on_expr(expr, p_psm); + p_psm->top_exprstate().on_expr(expr, p_psm); /* control here on input like: * (1.234; @@ -222,7 +220,7 @@ namespace xo { * f. now deliver semicolon; [lparen_1] rejects */ - p_stack->top_exprstate().on_semicolon_token(tk, p_psm); + p_psm->top_exprstate().on_semicolon_token(tk, p_psm); } void @@ -283,10 +281,10 @@ namespace xo { log && log(xtag("stack", p_stack)); - p_stack->top_exprstate().on_expr(expr, p_psm); + p_psm->top_exprstate().on_expr(expr, p_psm); /* now deliver rightparen */ - p_stack->top_exprstate().on_rightparen_token(tk, p_psm); + p_psm->top_exprstate().on_rightparen_token(tk, p_psm); } namespace { From fa9f4967f2389619baa82b436b7031f1f5298594 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 15:58:35 -0400 Subject: [PATCH 1437/2524] xo-reader: + parserstatemachine.push_exprstate(); simplify lambda_xs --- include/xo/reader/lambda_xs.hpp | 2 +- include/xo/reader/parserstatemachine.hpp | 1 + src/reader/expect_expr_xs.cpp | 7 +------ src/reader/lambda_xs.cpp | 11 ++++------- src/reader/parserstatemachine.cpp | 10 ++++++++++ 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/include/xo/reader/lambda_xs.hpp b/include/xo/reader/lambda_xs.hpp index 5ad0adfb..79c084ab 100644 --- a/include/xo/reader/lambda_xs.hpp +++ b/include/xo/reader/lambda_xs.hpp @@ -40,7 +40,7 @@ namespace xo { public: lambda_xs(); - static void start(exprstatestack * p_stack, rp * p_emit_expr); + static void start(parserstatemachine * p_psm); virtual void on_lambda_token(const token_type & tk, parserstatemachine * p_psm) override; diff --git a/include/xo/reader/parserstatemachine.hpp b/include/xo/reader/parserstatemachine.hpp index c5222194..c7ca130c 100644 --- a/include/xo/reader/parserstatemachine.hpp +++ b/include/xo/reader/parserstatemachine.hpp @@ -27,6 +27,7 @@ namespace xo { std::unique_ptr pop_exprstate(); exprstate & top_exprstate(); + void push_exprstate(std::unique_ptr x); public: exprstatestack * p_stack_; diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index bdd66b1a..2ec95272 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -41,13 +41,8 @@ namespace xo { //constexpr const char * self_name = "exprstate::on_leftparen"; - auto p_stack = p_psm->p_stack_; - auto p_emit_expr = p_psm->p_emit_expr_; - /* push lparen_0 to remember to look for subsequent rightparen. */ - lambda_xs::start(p_stack, p_emit_expr); - //p_psm->top_exprstate().on_lambda_token(tk, p_stack, p_emit_expr); - //p_stack->push_exprstate(expect_expr_xs::expect_rhs_expression()); + lambda_xs::start(p_psm); } void diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index 1ad6f401..491ab908 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -17,14 +17,11 @@ namespace xo { } void - lambda_xs::start(exprstatestack * p_stack, - rp * p_emit_expr) + lambda_xs::start(parserstatemachine * p_psm) { - parserstatemachine psm(p_stack, p_emit_expr); - - p_stack->push_exprstate(lambda_xs::make()); - p_stack->top_exprstate() - .on_lambda_token(token_type::lambda(), &psm); + p_psm->push_exprstate(lambda_xs::make()); + p_psm->top_exprstate() + .on_lambda_token(token_type::lambda(), p_psm); } lambda_xs::lambda_xs() : exprstate(exprstatetype::lambdaexpr) {} diff --git a/src/reader/parserstatemachine.cpp b/src/reader/parserstatemachine.cpp index 6d47b909..103ac15b 100644 --- a/src/reader/parserstatemachine.cpp +++ b/src/reader/parserstatemachine.cpp @@ -12,6 +12,16 @@ namespace xo { parserstatemachine::pop_exprstate() { return p_stack_->pop_exprstate(); } + + exprstate & + parserstatemachine::top_exprstate() { + return p_stack_->top_exprstate(); + } + + void + parserstatemachine::push_exprstate(std::unique_ptr x) { + p_stack_->push_exprstate(std::move(x)); + } } /*namespace scm*/ } /*namespace xo*/ From f5a309d6116be6b0d1b1bfbe20a23fa3247a5622 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 15:59:42 -0400 Subject: [PATCH 1438/2524] xo-reader: simplify: define_xs::start() --- src/reader/define_xs.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 4672bc96..43d700b1 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -17,9 +17,7 @@ namespace xo { void define_xs::start(parserstatemachine * p_psm) { - auto p_stack = p_psm->p_stack_; - - p_stack->push_exprstate(define_xs::make()); + p_psm->push_exprstate(define_xs::make()); p_psm->top_exprstate().on_def_token(token_type::def(), p_psm); } From c8f166acc84b6052752630cea142d64f1c6d11b6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 16:19:23 -0400 Subject: [PATCH 1439/2524] xo-reader: streamline exprseq_xs.start() using psm --- include/xo/reader/exprseq_xs.hpp | 2 +- src/reader/exprseq_xs.cpp | 4 ++-- src/reader/parser.cpp | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/include/xo/reader/exprseq_xs.hpp b/include/xo/reader/exprseq_xs.hpp index e1808a15..05d0a6e3 100644 --- a/include/xo/reader/exprseq_xs.hpp +++ b/include/xo/reader/exprseq_xs.hpp @@ -18,7 +18,7 @@ namespace xo { public: exprseq_xs(); - static void start(exprstatestack * p_stack); + static void start(parserstatemachine * p_psm); public: // ----- token input methods ----- diff --git a/src/reader/exprseq_xs.cpp b/src/reader/exprseq_xs.cpp index 0db1a394..b934c2ef 100644 --- a/src/reader/exprseq_xs.cpp +++ b/src/reader/exprseq_xs.cpp @@ -15,9 +15,9 @@ namespace xo { } void - exprseq_xs::start(exprstatestack * p_stack) + exprseq_xs::start(parserstatemachine * p_psm) { - p_stack->push_exprstate(exprseq_xs::make()); + p_psm->push_exprstate(exprseq_xs::make()); } exprseq_xs::exprseq_xs() diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index 9c9d373e..9301f1df 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -31,7 +31,11 @@ namespace xo { void parser::begin_translation_unit() { - exprseq_xs::start(&xs_stack_); + /* note: not using emit expr here */ + parserstatemachine psm(&xs_stack_, + nullptr /*p_emit_expr*/); + + exprseq_xs::start(&psm); } rp From 96c0bea2f54e4723f61878b5799ea272d3a715d8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 16:42:52 -0400 Subject: [PATCH 1440/2524] xo-reader: simplify expect_formal_xs, expect_symbol_xs --- include/xo/reader/expect_formal_xs.hpp | 2 +- include/xo/reader/expect_symbol_xs.hpp | 2 +- src/reader/define_xs.cpp | 2 +- src/reader/expect_formal_arglist_xs.cpp | 8 ++------ src/reader/expect_formal_xs.cpp | 6 +++--- src/reader/expect_symbol_xs.cpp | 4 ++-- 6 files changed, 10 insertions(+), 14 deletions(-) diff --git a/include/xo/reader/expect_formal_xs.hpp b/include/xo/reader/expect_formal_xs.hpp index 64663239..97d781ee 100644 --- a/include/xo/reader/expect_formal_xs.hpp +++ b/include/xo/reader/expect_formal_xs.hpp @@ -47,7 +47,7 @@ namespace xo { public: expect_formal_xs(); - static void start(exprstatestack * p_stack); + static void start(parserstatemachine * p_psm); virtual void on_symbol(const std::string & symbol_name, parserstatemachine * p_psm) override; diff --git a/include/xo/reader/expect_symbol_xs.hpp b/include/xo/reader/expect_symbol_xs.hpp index 63740aeb..be4df5e4 100644 --- a/include/xo/reader/expect_symbol_xs.hpp +++ b/include/xo/reader/expect_symbol_xs.hpp @@ -20,7 +20,7 @@ namespace xo { static std::unique_ptr make(); - static void start(exprstatestack * p_stack); + static void start(parserstatemachine * p_psm); virtual void on_symbol_token(const token_type & tk, parserstatemachine * p_psm) override; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 43d700b1..460d98d2 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -97,7 +97,7 @@ namespace xo { if (this->defxs_type_ == defexprstatetype::def_0) { this->defxs_type_ = defexprstatetype::def_1; - expect_symbol_xs::start(p_psm->p_stack_); + expect_symbol_xs::start(p_psm); } else { exprstate::on_def_token(tk, p_psm); } diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index 2f9f0fce..8e455413 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -52,12 +52,10 @@ namespace xo { expect_formal_arglist_xs::on_leftparen_token(const token_type & tk, parserstatemachine * p_psm) { - auto p_stack = p_psm->p_stack_; - if (farglxs_type_ == formalarglstatetype::argl_0) { this->farglxs_type_ = formalarglstatetype::argl_1a; /* TODO: refactor to have setup method on each exprstate */ - expect_formal_xs::start(p_stack); + expect_formal_xs::start(p_psm); } else { exprstate::on_leftparen_token(tk, p_psm); } @@ -79,11 +77,9 @@ namespace xo { expect_formal_arglist_xs::on_comma_token(const token_type & tk, parserstatemachine * p_psm) { - auto p_stack = p_psm->p_stack_; - if (farglxs_type_ == formalarglstatetype::argl_1b) { this->farglxs_type_ = formalarglstatetype::argl_1a; - expect_formal_xs::start(p_stack); + expect_formal_xs::start(p_psm); } else { exprstate::on_comma_token(tk, p_psm); } diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp index 7bd8773d..e60b77b0 100644 --- a/src/reader/expect_formal_xs.cpp +++ b/src/reader/expect_formal_xs.cpp @@ -38,10 +38,10 @@ namespace xo { } void - expect_formal_xs::start(exprstatestack * p_stack) { - p_stack->push_exprstate(expect_formal_xs::make()); + expect_formal_xs::start(parserstatemachine * p_psm) { + p_psm->push_exprstate(expect_formal_xs::make()); - expect_symbol_xs::start(p_stack); + expect_symbol_xs::start(p_psm); } expect_formal_xs::expect_formal_xs() diff --git a/src/reader/expect_symbol_xs.cpp b/src/reader/expect_symbol_xs.cpp index 3c8bfd2d..930d12b0 100644 --- a/src/reader/expect_symbol_xs.cpp +++ b/src/reader/expect_symbol_xs.cpp @@ -15,9 +15,9 @@ namespace xo { } void - expect_symbol_xs::start(exprstatestack * p_stack) + expect_symbol_xs::start(parserstatemachine * p_psm) { - p_stack->push_exprstate(expect_symbol_xs::make()); + p_psm->push_exprstate(expect_symbol_xs::make()); } expect_symbol_xs::expect_symbol_xs() From 6dcc0d420e0e16b31cf5967208bdcab7fd1212c6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 16:45:31 -0400 Subject: [PATCH 1441/2524] xo-reader: simplify expect_formal_arglist_xs.start() using psm --- include/xo/reader/expect_formal_arglist_xs.hpp | 2 +- src/reader/expect_formal_arglist_xs.cpp | 4 ++-- src/reader/lambda_xs.cpp | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/include/xo/reader/expect_formal_arglist_xs.hpp b/include/xo/reader/expect_formal_arglist_xs.hpp index 5bf75a8f..f3a6bf22 100644 --- a/include/xo/reader/expect_formal_arglist_xs.hpp +++ b/include/xo/reader/expect_formal_arglist_xs.hpp @@ -53,7 +53,7 @@ namespace xo { public: expect_formal_arglist_xs(); - static void start(exprstatestack * p_stack); + static void start(parserstatemachine * p_psm); virtual void on_leftparen_token(const token_type & tk, parserstatemachine * p_psm) override; diff --git a/src/reader/expect_formal_arglist_xs.cpp b/src/reader/expect_formal_arglist_xs.cpp index 8e455413..916841da 100644 --- a/src/reader/expect_formal_arglist_xs.cpp +++ b/src/reader/expect_formal_arglist_xs.cpp @@ -38,9 +38,9 @@ namespace xo { } void - expect_formal_arglist_xs::start(exprstatestack * p_stack) + expect_formal_arglist_xs::start(parserstatemachine * p_psm) { - p_stack->push_exprstate(expect_formal_arglist_xs::make()); + p_psm->push_exprstate(expect_formal_arglist_xs::make()); } expect_formal_arglist_xs::expect_formal_arglist_xs() diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index 491ab908..8fd2f46f 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -30,11 +30,9 @@ namespace xo { lambda_xs::on_lambda_token(const token_type & tk, parserstatemachine * p_psm) { - auto p_stack = p_psm->p_stack_; - if (lmxs_type_ == lambdastatetype::lm_0) { this->lmxs_type_ = lambdastatetype::lm_1; - expect_formal_arglist_xs::start(p_stack); + expect_formal_arglist_xs::start(p_psm); } else { exprstate::on_lambda_token(tk, p_psm); } From 0b0c424b8422892210ec0b9d87fb2557beb586b0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 16:48:58 -0400 Subject: [PATCH 1442/2524] xo-reader: simplify expect_expr_xs,expect_lparen_xs using qsm --- include/xo/reader/expect_expr_xs.hpp | 2 +- include/xo/reader/paren_xs.hpp | 2 +- src/reader/define_xs.cpp | 2 +- src/reader/expect_expr_xs.cpp | 8 +++----- src/reader/lambda_xs.cpp | 4 +--- src/reader/paren_xs.cpp | 6 +++--- src/reader/progress_xs.cpp | 8 ++++---- 7 files changed, 14 insertions(+), 18 deletions(-) diff --git a/include/xo/reader/expect_expr_xs.hpp b/include/xo/reader/expect_expr_xs.hpp index 90477c72..650a3195 100644 --- a/include/xo/reader/expect_expr_xs.hpp +++ b/include/xo/reader/expect_expr_xs.hpp @@ -17,7 +17,7 @@ namespace xo { public: expect_expr_xs(); - static void start(exprstatestack * p_stack); + static void start(parserstatemachine * p_psm); virtual void on_lambda_token(const token_type & tk, parserstatemachine * p_psm) override; diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 0fd6bf86..429a2a6e 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -29,7 +29,7 @@ namespace xo { static const paren_xs * from(const exprstate * x) { return dynamic_cast(x); } - static void start(exprstatestack * p_stack); + static void start(parserstatemachine * p_psm); bool admits_f64() const; bool admits_rightparen() const; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 460d98d2..4a21a0d7 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -172,7 +172,7 @@ namespace xo { { this->defxs_type_ = defexprstatetype::def_5; - expect_expr_xs::start(p_psm->p_stack_); + expect_expr_xs::start(p_psm); } else { this->illegal_input_error(self_name, tk); } diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index 2ec95272..cd4f4cf5 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -24,8 +24,8 @@ namespace xo { } void - expect_expr_xs::start(exprstatestack * p_stack) { - p_stack->push_exprstate(expect_expr_xs::make()); + expect_expr_xs::start(parserstatemachine * p_psm) { + p_psm->push_exprstate(expect_expr_xs::make()); } expect_expr_xs::expect_expr_xs() @@ -54,10 +54,8 @@ namespace xo { //constexpr const char * self_name = "exprstate::on_leftparen"; - auto p_stack = p_psm->p_stack_; - /* push lparen_0 to remember to look for subsequent rightparen. */ - paren_xs::start(p_stack); + paren_xs::start(p_psm); } void diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index 8fd2f46f..a134a23d 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -42,12 +42,10 @@ namespace xo { lambda_xs::on_formal_arglist(const std::vector> & argl, parserstatemachine * p_psm) { - auto p_stack = p_psm->p_stack_; - if (lmxs_type_ == lambdastatetype::lm_1) { this->lmxs_type_ = lambdastatetype::lm_2; this->argl_ = argl; - expect_expr_xs::start(p_stack); + expect_expr_xs::start(p_psm); } else { exprstate::on_formal_arglist(argl, p_psm); } diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index d1d9535c..f48d9d49 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -18,10 +18,10 @@ namespace xo { } void - paren_xs::start(exprstatestack * p_stack) + paren_xs::start(parserstatemachine * p_psm) { - p_stack->push_exprstate(paren_xs::make()); - expect_expr_xs::start(p_stack); + p_psm->push_exprstate(paren_xs::make()); + expect_expr_xs::start(p_psm); } bool diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 6f62f853..3ad4144f 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -319,7 +319,7 @@ namespace xo { this->op_type_ = tk2op(tk.tk_type()); /* infix operator must be followed by non-empty expression */ - expect_expr_xs::start(p_stack); + expect_expr_xs::start(p_psm); } else if (rhs_) { /* already have complete expression stashed. * behavior depends on operator precedence for tk with stored operator @@ -348,7 +348,7 @@ namespace xo { progress_xs::start(expr, op2, p_stack); /* infix operator must be followed by non-empty expression */ - expect_expr_xs::start(p_stack); + expect_expr_xs::start(p_psm); } else { /* e.g. * 6.2 + 4.9 * ... @@ -367,9 +367,9 @@ namespace xo { /* 1. replace with nested incomplete infix exprs */ progress_xs::start(lhs_, op_type_, p_stack); - expect_expr_xs::start(p_stack); + expect_expr_xs::start(p_psm); progress_xs::start(rhs_, op2, p_stack); - expect_expr_xs::start(p_stack); + expect_expr_xs::start(p_psm); } } else { From e74e55832e92516f081da571fb67ec265900d3e3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 16:50:58 -0400 Subject: [PATCH 1443/2524] xo-reader: simplify expect_type_xs.start() using psm --- include/xo/reader/expect_type_xs.hpp | 2 +- src/reader/define_xs.cpp | 4 +--- src/reader/expect_formal_xs.cpp | 4 +--- src/reader/expect_type_xs.cpp | 4 ++-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/include/xo/reader/expect_type_xs.hpp b/include/xo/reader/expect_type_xs.hpp index 86e5adf7..1a388f54 100644 --- a/include/xo/reader/expect_type_xs.hpp +++ b/include/xo/reader/expect_type_xs.hpp @@ -16,7 +16,7 @@ namespace xo { public: expect_type_xs(); - static void start(exprstatestack * p_stack); + static void start(parserstatemachine * p_stack); virtual void on_symbol_token(const token_type & tk, parserstatemachine * p_psm) override; diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 4a21a0d7..04c37c5b 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -112,12 +112,10 @@ namespace xo { //constexpr const char * self_name = "define_xs::on_colon_token"; - auto p_stack = p_psm->p_stack_; - if (this->defxs_type_ == defexprstatetype::def_2) { this->defxs_type_ = defexprstatetype::def_3; - expect_type_xs::start(p_stack); + expect_type_xs::start(p_psm); } else { exprstate::on_colon_token(tk, p_psm); } diff --git a/src/reader/expect_formal_xs.cpp b/src/reader/expect_formal_xs.cpp index e60b77b0..28e19ce4 100644 --- a/src/reader/expect_formal_xs.cpp +++ b/src/reader/expect_formal_xs.cpp @@ -64,11 +64,9 @@ namespace xo { expect_formal_xs::on_colon_token(const token_type & tk, parserstatemachine * p_psm) { - auto p_stack = p_psm->p_stack_; - if (this->formalxs_type_ == formalstatetype::formal_1) { this->formalxs_type_ = formalstatetype::formal_2; - expect_type_xs::start(p_stack); + expect_type_xs::start(p_psm); /* control reenters via expect_formal_xs::on_typedescr() */ } else { exprstate::on_colon_token(tk, diff --git a/src/reader/expect_type_xs.cpp b/src/reader/expect_type_xs.cpp index f8efd265..d994257e 100644 --- a/src/reader/expect_type_xs.cpp +++ b/src/reader/expect_type_xs.cpp @@ -19,8 +19,8 @@ namespace xo { } void - expect_type_xs::start(exprstatestack * p_stack) { - p_stack->push_exprstate(expect_type_xs::make()); + expect_type_xs::start(parserstatemachine * p_psm) { + p_psm->push_exprstate(expect_type_xs::make()); } expect_type_xs::expect_type_xs() From 8db0bf11d0f8a88512a556f0134cbb29b629ca01 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 16:53:43 -0400 Subject: [PATCH 1444/2524] xo-reader: simplify progress_xs.start() using psm --- include/xo/reader/progress_xs.hpp | 4 ++-- src/reader/expect_expr_xs.cpp | 2 +- src/reader/paren_xs.cpp | 4 +--- src/reader/progress_xs.cpp | 16 +++++++--------- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 6436b117..b370a1f9 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -49,9 +49,9 @@ namespace xo { static void start(rp valex, optype optype, - exprstatestack * p_stack); + parserstatemachine * p_psm); static void start(rp valex, - exprstatestack * p_stack); + parserstatemachine * p_psm); bool admits_f64() const; diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index cd4f4cf5..eed3b006 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -107,7 +107,7 @@ namespace xo { */ progress_xs::start (Constant::make(tk.f64_value()), - p_psm->p_stack_); + p_psm); } void diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index f48d9d49..5b1974cc 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -181,9 +181,7 @@ namespace xo { switch (this->parenxs_type_) { case parenexprstatetype::lparen_0: { this->parenxs_type_ = parenexprstatetype::lparen_1; /* wants on_rightparen */ - auto p_stack = p_psm->p_stack_; - - progress_xs::start(expr.promote(), p_stack); + progress_xs::start(expr.promote(), p_psm); return; } diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 3ad4144f..ed873e35 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -55,13 +55,13 @@ namespace xo { } void - progress_xs::start(rp valex, optype op, exprstatestack * p_stack) { - p_stack->push_exprstate(progress_xs::make(valex, op)); + progress_xs::start(rp valex, optype op, parserstatemachine * p_psm) { + p_psm->push_exprstate(progress_xs::make(valex, op)); } void - progress_xs::start(rp valex, exprstatestack * p_stack) { - p_stack->push_exprstate(progress_xs::make(valex)); + progress_xs::start(rp valex, parserstatemachine * p_psm) { + p_psm->push_exprstate(progress_xs::make(valex)); } progress_xs::progress_xs(rp valex, optype op) @@ -313,8 +313,6 @@ namespace xo { { constexpr const char * c_self_name = "progress_xs::on_operator_token"; - auto p_stack = p_psm->p_stack_; - if (op_type_ == optype::invalid) { this->op_type_ = tk2op(tk.tk_type()); @@ -345,7 +343,7 @@ namespace xo { std::unique_ptr self = p_psm->pop_exprstate(); /* 3. replace with new progress_xs: */ - progress_xs::start(expr, op2, p_stack); + progress_xs::start(expr, op2, p_psm); /* infix operator must be followed by non-empty expression */ expect_expr_xs::start(p_psm); @@ -366,9 +364,9 @@ namespace xo { std::unique_ptr self = p_psm->pop_exprstate(); /* 1. replace with nested incomplete infix exprs */ - progress_xs::start(lhs_, op_type_, p_stack); + progress_xs::start(lhs_, op_type_, p_psm); expect_expr_xs::start(p_psm); - progress_xs::start(rhs_, op2, p_stack); + progress_xs::start(rhs_, op2, p_psm); expect_expr_xs::start(p_psm); } From 8d495a642724d3fab6c6176687d98ff250000ab4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 17:28:35 -0400 Subject: [PATCH 1445/2524] xo-reader: wip: + envframe, envframestack [not used] --- include/xo/reader/envframe.hpp | 48 ++++++++++++++++++++ include/xo/reader/envframestack.hpp | 66 +++++++++++++++++++++++++++ src/reader/CMakeLists.txt | 5 ++- src/reader/envframe.cpp | 34 ++++++++++++++ src/reader/envframestack.cpp | 70 +++++++++++++++++++++++++++++ 5 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 include/xo/reader/envframe.hpp create mode 100644 include/xo/reader/envframestack.hpp create mode 100644 src/reader/envframe.cpp create mode 100644 src/reader/envframestack.cpp diff --git a/include/xo/reader/envframe.hpp b/include/xo/reader/envframe.hpp new file mode 100644 index 00000000..a0fcfb7d --- /dev/null +++ b/include/xo/reader/envframe.hpp @@ -0,0 +1,48 @@ +/* file envframe.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "xo/expression/Variable.hpp" +#include + +namespace xo { + namespace scm { + /** @class envframe + * @brief names/types of formal paremeters introduced by a function + * + * + **/ + class envframe { + public: + using Variable = xo::ast::Variable; + + public: + envframe() = default; + envframe(const std::vector> & argl) : argl_(argl) {} + + const std::vector> & argl() const { return argl_; } + + /** lookup variable by name. If found, return it. + * Otherwise return nullptr + **/ + rp lookup(const std::string & name) const; + + void print (std::ostream & os) const; + + private: + std::vector> argl_; + }; + + inline std::ostream & + operator<< (std::ostream & os, const envframe & x) { + x.print(os); + return os; + } + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end envframe.hpp */ diff --git a/include/xo/reader/envframestack.hpp b/include/xo/reader/envframestack.hpp new file mode 100644 index 00000000..d27713ac --- /dev/null +++ b/include/xo/reader/envframestack.hpp @@ -0,0 +1,66 @@ +/* file envframestack.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "envframe.hpp" + +namespace xo { + namespace scm { + /** @class envframestack + * @brief A stack of envframe objects + **/ + class envframestack { + public: + envframestack() {} + + bool empty() const { return stack_.empty(); } + std::size_t size() const { return stack_.size(); } + + envframe & top_envframe(); + void push_envframe(envframe x); + void pop_envframe(); + + /** relative to top-of-stack. + * 0 -> top (last in), z-1 -> bottom (first in) + **/ + envframe & operator[](std::size_t i) { + std::size_t z = stack_.size(); + + assert(i < z); + + return stack_[z - i - 1]; + } + + const envframe & operator[](std::size_t i) const { + std::size_t z = stack_.size(); + + assert(i < z); + + return stack_[z - i - 1]; + } + + void print (std::ostream & os) const; + + private: + std::vector stack_; + }; + + inline std::ostream & + operator<< (std::ostream & os, const envframestack & x) { + x.print(os); + return os; + } + + inline std::ostream & + operator<< (std::ostream & os, const envframestack * x) { + x->print(os); + return os; + } + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end envframestack.hpp */ diff --git a/src/reader/CMakeLists.txt b/src/reader/CMakeLists.txt index 5edbb2e4..83dc675d 100644 --- a/src/reader/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -13,11 +13,12 @@ set(SELF_SRCS exprseq_xs.cpp expect_expr_xs.cpp expect_symbol_xs.cpp - expect_formal_xs.cpp expect_formal_arglist_xs.cpp expect_type_xs.cpp - lambda_xs.cpp) + lambda_xs.cpp + envframestack.cpp + envframe.cpp) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_dependency(${SELF_LIB} xo_expression) diff --git a/src/reader/envframe.cpp b/src/reader/envframe.cpp new file mode 100644 index 00000000..38ff1fcb --- /dev/null +++ b/src/reader/envframe.cpp @@ -0,0 +1,34 @@ +/* file envframe.cpp + * + * author: Roland Conybeare + */ + +#include "envframe.hpp" +#include "xo/indentlog/print/vector.hpp" + +namespace xo { + using xo::ast::Variable; + + namespace scm { + rp + envframe::lookup(const std::string & x) const { + for (const auto & var : argl_) { + if (x == var->name()) + return var; + } + + return nullptr; + } + + void + envframe::print(std::ostream & os) const { + os << ""; + } + + } /*namespace scm */ +} /*namespace xo*/ + + +/* end envframe.cpp */ diff --git a/src/reader/envframestack.cpp b/src/reader/envframestack.cpp new file mode 100644 index 00000000..52543dc4 --- /dev/null +++ b/src/reader/envframestack.cpp @@ -0,0 +1,70 @@ +/* file envframestack.cpp + * + * author: Roland Conybeare + */ + +#include "envframestack.hpp" + +namespace xo { + namespace scm { + envframe & + envframestack::top_envframe() { + std::size_t z = stack_.size(); + + if (z == 0) { + throw std::runtime_error + ("parser::top_exprstate: unexpected empty stack"); + } + + return stack_[z-1]; + } + + void + envframestack::push_envframe(envframe frame) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag), + xtag("frame", frame)); + + std::size_t z = stack_.size(); + + stack_.resize(z+1); + + stack_[z] = std::move(frame); + } + + void + envframestack::pop_envframe() { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + std::size_t z = stack_.size(); + + if (z > 0) { + //std::unique_ptr top = std::move(stack_[z-1]); + + stack_.resize(z-1); + + //return top; + } else { + //return nullptr; + } + } + + void + envframestack::print(std::ostream & os) const { + os << "" << std::endl; + } + } /*namespace scm*/ +} /*namespace xo*/ + +/* end envframestack.cpp */ From 94109c93b7f51e9720cd2b7f84b5ee0451390cfa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 17:30:15 -0400 Subject: [PATCH 1446/2524] xo-reader: wip: + parser.env_stack [not used] --- include/xo/reader/parser.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/xo/reader/parser.hpp b/include/xo/reader/parser.hpp index dae3091f..30dd94ea 100644 --- a/include/xo/reader/parser.hpp +++ b/include/xo/reader/parser.hpp @@ -6,6 +6,7 @@ #pragma once #include "exprstatestack.hpp" +#include "envframestack.hpp" #include namespace xo { @@ -212,6 +213,12 @@ namespace xo { **/ exprstatestack xs_stack_; + /** environment frames for lexical context. + * push a frame on each nested lambda; + * pop when lambda body goes out of scope + **/ + envframestack env_stack_; + }; /*parser*/ inline std::ostream & From 3b57a1f142b461bb58bbcbb20ef1a7458999334e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 17:34:20 -0400 Subject: [PATCH 1447/2524] xo-reader: wip: add environment-frame stack to psm [not used] --- include/xo/reader/parserstatemachine.hpp | 12 +++++++++++- src/reader/parser.cpp | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/xo/reader/parserstatemachine.hpp b/include/xo/reader/parserstatemachine.hpp index c7ca130c..e39021f3 100644 --- a/include/xo/reader/parserstatemachine.hpp +++ b/include/xo/reader/parserstatemachine.hpp @@ -6,6 +6,7 @@ #pragma once #include "exprstate.hpp" +#include "envframestack.hpp" namespace xo { namespace scm { @@ -22,15 +23,24 @@ namespace xo { public: parserstatemachine(exprstatestack * p_stack, + envframestack * p_env_stack, rp * p_emit_expr) - : p_stack_{p_stack}, p_emit_expr_{p_emit_expr} {} + : p_stack_{p_stack}, + p_env_stack_{p_env_stack}, + p_emit_expr_{p_emit_expr} {} std::unique_ptr pop_exprstate(); exprstate & top_exprstate(); void push_exprstate(std::unique_ptr x); public: + /** stack of incomplete parser work. + * generally speaking, push when to start new work for nested content; + * pop when work complete + **/ exprstatestack * p_stack_; + /** stack of environment frames, one for each enclosing lambda **/ + envframestack * p_env_stack_; rp * p_emit_expr_; }; } /*namespace scm*/ diff --git a/src/reader/parser.cpp b/src/reader/parser.cpp index 9301f1df..e71deb40 100644 --- a/src/reader/parser.cpp +++ b/src/reader/parser.cpp @@ -33,6 +33,7 @@ namespace xo { parser::begin_translation_unit() { /* note: not using emit expr here */ parserstatemachine psm(&xs_stack_, + &env_stack_, nullptr /*p_emit_expr*/); exprseq_xs::start(&psm); @@ -55,7 +56,7 @@ namespace xo { rp retval; - parserstatemachine psm(&xs_stack_, &retval); + parserstatemachine psm(&xs_stack_, &env_stack_, &retval); xs_stack_.top_exprstate().on_input(tk, &psm); From eed5cdf6910d37b5318c43f0d3dce0591ff304fd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 18:11:28 -0400 Subject: [PATCH 1448/2524] xo-reader: feat: + var lookup in envframestack, psm --- include/xo/reader/envframestack.hpp | 9 +++++++++ include/xo/reader/parserstatemachine.hpp | 9 +++++++++ src/reader/envframestack.cpp | 16 ++++++++++++++++ src/reader/parserstatemachine.cpp | 17 +++++++++++++++++ 4 files changed, 51 insertions(+) diff --git a/include/xo/reader/envframestack.hpp b/include/xo/reader/envframestack.hpp index d27713ac..ac933dae 100644 --- a/include/xo/reader/envframestack.hpp +++ b/include/xo/reader/envframestack.hpp @@ -13,12 +13,21 @@ namespace xo { * @brief A stack of envframe objects **/ class envframestack { + public: + using Variable = xo::ast::Variable; + public: envframestack() {} bool empty() const { return stack_.empty(); } std::size_t size() const { return stack_.size(); } + /** lookup variable in environment stack. + * Visit frames in fifo order, report first match; + * nullptr if no matches. + **/ + rp lookup(const std::string & x) const; + envframe & top_envframe(); void push_envframe(envframe x); void pop_envframe(); diff --git a/include/xo/reader/parserstatemachine.hpp b/include/xo/reader/parserstatemachine.hpp index e39021f3..3ad71b13 100644 --- a/include/xo/reader/parserstatemachine.hpp +++ b/include/xo/reader/parserstatemachine.hpp @@ -20,6 +20,7 @@ namespace xo { class parserstatemachine { public: using Expression = xo::ast::Expression; + using Variable = xo::ast::Variable; public: parserstatemachine(exprstatestack * p_stack, @@ -33,6 +34,14 @@ namespace xo { exprstate & top_exprstate(); void push_exprstate(std::unique_ptr x); + /** lookup variable name in lexical context represented by + * this psm. nullptr if not found + **/ + rp lookup_var(const std::string & x) const; + + void push_envframe(envframe x); + void pop_envframe(); + public: /** stack of incomplete parser work. * generally speaking, push when to start new work for nested content; diff --git a/src/reader/envframestack.cpp b/src/reader/envframestack.cpp index 52543dc4..047e1789 100644 --- a/src/reader/envframestack.cpp +++ b/src/reader/envframestack.cpp @@ -6,6 +6,8 @@ #include "envframestack.hpp" namespace xo { + using xo::ast::Variable; + namespace scm { envframe & envframestack::top_envframe() { @@ -50,6 +52,20 @@ namespace xo { } } + rp + envframestack::lookup(const std::string & x) const { + for (std::size_t i = 0, z = this->size(); i < z; ++i) { + const auto & frame = (*this)[i]; + + auto retval = frame.lookup(x); + + if (retval) + return retval; + } + + return nullptr; + } + void envframestack::print(std::ostream & os) const { os << " + parserstatemachine::lookup_var(const std::string & x) const { + return p_env_stack_->lookup(x); + } + std::unique_ptr parserstatemachine::pop_exprstate() { return p_stack_->pop_exprstate(); @@ -22,6 +29,16 @@ namespace xo { parserstatemachine::push_exprstate(std::unique_ptr x) { p_stack_->push_exprstate(std::move(x)); } + + void + parserstatemachine::push_envframe(envframe x) { + p_env_stack_->push_envframe(std::move(x)); + } + + void + parserstatemachine::pop_envframe() { + p_env_stack_->pop_envframe(); + } } /*namespace scm*/ } /*namespace xo*/ From fbc21222715f1203ef559a39537c7fc44f68740a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 18:33:25 -0400 Subject: [PATCH 1449/2524] xo-reader: wip: push/pop env frames for lambdas --- src/reader/lambda_xs.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index a134a23d..00fb5c0e 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -45,6 +45,9 @@ namespace xo { if (lmxs_type_ == lambdastatetype::lm_1) { this->lmxs_type_ = lambdastatetype::lm_2; this->argl_ = argl; + + p_psm->push_envframe(envframe(argl)); + expect_expr_xs::start(p_psm); } else { exprstate::on_formal_arglist(argl, p_psm); @@ -76,6 +79,8 @@ namespace xo { rp lm = Lambda::make(name, argl_, body_); + p_psm->pop_envframe(); + p_psm->top_exprstate().on_expr(lm, p_psm); p_psm->top_exprstate().on_semicolon_token(tk, p_psm); From 50dd94e354cfc6e9d55b87a5b124bb1ef33feeee Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 18:33:54 -0400 Subject: [PATCH 1450/2524] xo-reader: wip: expect_expr looks up symbols --- src/reader/expect_expr_xs.cpp | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index eed3b006..9896421e 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -59,8 +59,8 @@ namespace xo { } void - expect_expr_xs::on_symbol_token(const token_type & /*tk*/, - parserstatemachine * /*p_psm*/) + expect_expr_xs::on_symbol_token(const token_type & tk, + parserstatemachine * p_psm) { /* todo: treat symbol as variable name */ @@ -75,9 +75,26 @@ namespace xo { * and {(2), (3)} (symbol is function call) */ - /* have to do pop first, before sending symbol to - * the o.g. symbol-requester + rp var = p_psm->lookup_var(tk.text()); + + if (!var) { + throw std::runtime_error + (tostr("expect_expr_xs::on_symbol_token", + ": unknown symbol", + xtag("name", tk.text()))); + } + + /* e.g. + * def pi = 3.14159265; + * def mypi = pi; + * ^ + * def pi2 = pi * 2; + * ^ + * def y = foo(pi2); + * ^ */ + progress_xs::start(var, p_psm); + #ifdef NOT_YET p_stack->push_exprstate(exprstate(exprstatetype::expr_progress, Variable::make(name, type))); From e712169daac6445debe2049fcb771589133b7a9c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 18:35:27 -0400 Subject: [PATCH 1451/2524] xo-reader: utest: + test variable lookup [working] --- utest/reader.test.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utest/reader.test.cpp b/utest/reader.test.cpp index aa85fcc7..2c9a490d 100644 --- a/utest/reader.test.cpp +++ b/utest/reader.test.cpp @@ -17,7 +17,8 @@ namespace xo { {"def foo : f64 = (3.14159265);"}, //{"def foo : f64 = 2.0 * 3.14159265;"}, {"def foo = lambda (x : f64) 3.1415965;"}, - {"def foo = lambda (x : f64, y : f64) 3.1415965;"} + {"def foo = lambda (x : f64, y : f64) 3.1415965;"}, + {"def foo = lambda (x : f64) x;"} }; } From d4fd55b8ed79591dba390ba54190cc3e52e6caf6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 18:45:02 -0400 Subject: [PATCH 1452/2524] xo-expression: arithmetic expression support --- include/xo/expression/Apply.hpp | 13 ++++ include/xo/expression/Primitive.hpp | 29 +++++++- include/xo/expression/llvmintrinsic.hpp | 4 ++ src/expression/Apply.cpp | 33 +++++++++ src/expression/CMakeLists.txt | 1 + src/expression/Primitive.cpp | 89 +++++++++++++++++++++++++ src/expression/intrinsics.cpp | 14 ++++ 7 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 src/expression/Primitive.cpp create mode 100644 src/expression/intrinsics.cpp diff --git a/include/xo/expression/Apply.hpp b/include/xo/expression/Apply.hpp index f06b8b8b..e95dd76a 100644 --- a/include/xo/expression/Apply.hpp +++ b/include/xo/expression/Apply.hpp @@ -28,6 +28,19 @@ namespace xo { static rp make(const rp & fn, const std::vector> & argv); + /** create apply-expression to add two 64-bit floating-point numbers **/ + static rp make_add2_f64(const rp & lhs, + const rp & rhs); + /** create apply-expression to subtract two 64-bit floating-point numbers **/ + static rp make_sub2_f64(const rp & lhs, + const rp & rhs); + /** create apply-expression to multiply two 64-bit floating-point numbers **/ + static rp make_mul2_f64(const rp & lhs, + const rp & rhs); + /** create apply-expression to divide two 64-bit floating-point numbers **/ + static rp make_div2_f64(const rp & lhs, + const rp & rhs); + /** downcast from Expression **/ static ref::brw from(ref::brw x) { return ref::brw::from(x); diff --git a/include/xo/expression/Primitive.hpp b/include/xo/expression/Primitive.hpp index 5b1d46ac..2305bfe4 100644 --- a/include/xo/expression/Primitive.hpp +++ b/include/xo/expression/Primitive.hpp @@ -10,6 +10,13 @@ #include "xo/reflect/Reflect.hpp" //#include +extern "C" { + /* these symbols needed to link primitives */ + + /* see Primitive_f64::make() */ + double add2_f64(double x, double y); +}; + namespace xo { namespace ast { /** @class Primitive @@ -35,6 +42,7 @@ namespace xo { using Reflect = xo::reflect::Reflect; using TaggedPtr = xo::reflect::TaggedPtr; using TypeDescr = xo::reflect::TypeDescr; + using fptr_type = FunctionPointer; public: static rp make(const std::string & name, @@ -46,8 +54,9 @@ namespace xo { return new Primitive(fn_type, name, fnptr, explicit_symbol_def, intrinsic); } + /** see classes below for intrinsics **/ + FunctionPointer value() const { return value_; } - llvmintrinsic intrinsic() const { return intrinsic_; } TypeDescr value_td() const { return value_td_; } TaggedPtr value_tp() const { @@ -60,6 +69,7 @@ namespace xo { // ----- PrimitiveInterface ----- + virtual llvmintrinsic intrinsic() const override { return intrinsic_; } virtual bool explicit_symbol_def() const override { return explicit_symbol_def_; } virtual void_function_type function_address() const override { return reinterpret_cast(value_); } @@ -113,7 +123,7 @@ namespace xo { /** for LLVM: if true, use Jit.intern_symbol() to provide explicit binding. * * Not obvious what distinguishes functions like ::sin(), ::sqrt() - * (which work without this) from symbols like ::mul_i32(), which do. + * (which work without this) from symbols like ::mul_i32(), which require it. **/ bool explicit_symbol_def_ = false; /** invalid: generate call (IRBuilder::CreateCall) @@ -132,6 +142,21 @@ namespace xo { { return Primitive::make(name, x, explicit_symbol_def, intrinsic); } + + class Primitive_f64 : public Primitive { + public: + using PrimitiveType = Primitive; + + public: + /** add2_f64: add two 64-bit floating-point numbers **/ + static rp make_add2_f64(); + /** sub2_f64: subtract two 64-bit floating-point numbers **/ + static rp make_sub2_f64(); + /** mul2_f64: multiply two 64-bit floating-point numbers **/ + static rp make_mul2_f64(); + /** div2_f64: divide two 64-bit floating-point numbers **/ + static rp make_div2_f64(); + }; } /*namespace ast*/ } /*namespace xo*/ diff --git a/include/xo/expression/llvmintrinsic.hpp b/include/xo/expression/llvmintrinsic.hpp index 052baa9e..bc484640 100644 --- a/include/xo/expression/llvmintrinsic.hpp +++ b/include/xo/expression/llvmintrinsic.hpp @@ -65,6 +65,9 @@ namespace xo { /** -> IRBuilder::CreateFadd (add 2 floating-point numbers) **/ fp_add, + /** -> IRBuilder::CreateFsub (subtract 2 floating-pointer numbers) **/ + fp_sub, + /** -> IRBuilder::CreateFmul (multiply 2 floating-point numbers) **/ fp_mul, @@ -105,6 +108,7 @@ namespace xo { case llvmintrinsic::i_sdiv: return "i_sdiv"; case llvmintrinsic::i_udiv: return "i_udiv"; case llvmintrinsic::fp_add: return "fp_add"; + case llvmintrinsic::fp_sub: return "fp_sub"; case llvmintrinsic::fp_mul: return "fp_mul"; case llvmintrinsic::fp_div: return "fp_div"; case llvmintrinsic::fp_sqrt: return "fp_sqrt"; diff --git a/src/expression/Apply.cpp b/src/expression/Apply.cpp index 36e2428a..497565b1 100644 --- a/src/expression/Apply.cpp +++ b/src/expression/Apply.cpp @@ -1,6 +1,7 @@ /* @file Apply.cpp */ #include "Apply.hpp" +#include "Primitive.hpp" #include "xo/indentlog/print/vector.hpp" namespace xo { @@ -25,6 +26,38 @@ namespace xo { return new Apply(fn_retval_type, fn, argv); } + rp + Apply::make_add2_f64(const rp & lhs, + const rp & rhs) + { + return Apply::make(Primitive_f64::make_add2_f64(), + {lhs, rhs}); + } + + rp + Apply::make_sub2_f64(const rp & lhs, + const rp & rhs) + { + return Apply::make(Primitive_f64::make_sub2_f64(), + {lhs, rhs}); + } + + rp + Apply::make_mul2_f64(const rp & lhs, + const rp & rhs) + { + return Apply::make(Primitive_f64::make_mul2_f64(), + {lhs, rhs}); + } + + rp + Apply::make_div2_f64(const rp & lhs, + const rp & rhs) + { + return Apply::make(Primitive_f64::make_div2_f64(), + {lhs, rhs}); + } + void Apply::display(std::ostream & os) const { os << " rp + { + static rp s_retval; + + if (!s_retval) + s_retval = Primitive::make("add2_f64", + &add2_f64, + true /*explicit_symbol_def*/, + llvmintrinsic::fp_add); + + return s_retval; + } + + auto + Primitive_f64::make_sub2_f64() -> rp + { + static rp s_retval; + + if (!s_retval) + s_retval = Primitive::make("sub2_f64", + &sub2_f64, + true /*explicit_symbol_def*/, + llvmintrinsic::fp_sub); + + return s_retval; + } + + auto + Primitive_f64::make_mul2_f64() -> rp + { + static rp s_retval; + + if (!s_retval) + s_retval = Primitive::make("mul2_f64", + &mul2_f64, + true /*explicit_symbol_def*/, + llvmintrinsic::fp_mul); + + return s_retval; + } + + auto + Primitive_f64::make_div2_f64() -> rp + { + static rp s_retval; + + if (!s_retval) + s_retval = Primitive::make("div2_f64", + &div2_f64, + true /*explicit_symbol_def*/, + llvmintrinsic::fp_div); + + return s_retval; + } + + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end Primitive.cpp */ diff --git a/src/expression/intrinsics.cpp b/src/expression/intrinsics.cpp new file mode 100644 index 00000000..27dcd83a --- /dev/null +++ b/src/expression/intrinsics.cpp @@ -0,0 +1,14 @@ +/* @file intrinsics.cpp */ + +#include "intrinsics.hpp" + +/* FIXME: don't know how to mangle symbols yet, + * so putting functions invoked from jit into global namespace + */ +extern "C" +int32_t +mul_i32(int32_t x, int32_t y) { + return x * y; +} + +/* end intrinsics.cpp */ From b1c3dc80b15af53989a78f3454f248f20d5beb31 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 19 Aug 2024 18:50:35 -0400 Subject: [PATCH 1453/2524] xo-reflect: + invoker feature --- include/xo/reflect/Reflect.hpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/include/xo/reflect/Reflect.hpp b/include/xo/reflect/Reflect.hpp index d4cfd34c..f75c3e44 100644 --- a/include/xo/reflect/Reflect.hpp +++ b/include/xo/reflect/Reflect.hpp @@ -182,11 +182,9 @@ namespace xo { } else { /* control comes here the first time require() runs */ - static detail::InvokerAux s_final_invoker; - auto final_tdx = EstablishTdx::make(); - retval_td->assign_tdextra(&s_final_invoker, + retval_td->assign_tdextra(Reflect::get_final_invoker(), std::move(final_tdx)); /* also need to require for each child */ @@ -222,7 +220,8 @@ namespace xo { auto final_tdx = EstablishFunctionTdx::make(); - retval_td->assign_tdextra(std::move(final_tdx)); + retval_td->assign_tdextra(Reflect::get_final_invoker(), + std::move(final_tdx)); /* also need to require for each child */ } @@ -254,6 +253,16 @@ namespace xo { template static TaggedRcptr make_rctp(T * x) { return TaggedPtrMaker::make_rctp(x); } + + private: + + template + static detail::InvokerAux * get_final_invoker() { + static detail::InvokerAux s_final_invoker; + + return &s_final_invoker; + } + }; /*Reflect*/ // ----- MakeTagged ----- From 5038045bdc9749cf4c1a025e696f65db1538cca6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 20 Aug 2024 13:31:17 -0400 Subject: [PATCH 1454/2524] lambda stuff [wip] --- include/xo/expression/Expression.hpp | 32 +------- .../xo/expression/GeneralizedExpression.hpp | 60 ++++++++++++++ include/xo/expression/Lambda.hpp | 31 ++++++++ include/xo/expression/Sequence.hpp | 52 +++++++++++++ include/xo/expression/exprtype.hpp | 3 + src/expression/CMakeLists.txt | 2 + src/expression/Expression.cpp | 4 - src/expression/GeneralizedExpression.cpp | 14 ++++ src/expression/Lambda.cpp | 74 ++++++++++++++++-- src/expression/Sequence.cpp | 78 +++++++++++++++++++ 10 files changed, 309 insertions(+), 41 deletions(-) create mode 100644 include/xo/expression/GeneralizedExpression.hpp create mode 100644 include/xo/expression/Sequence.hpp create mode 100644 src/expression/GeneralizedExpression.cpp create mode 100644 src/expression/Sequence.cpp diff --git a/include/xo/expression/Expression.hpp b/include/xo/expression/Expression.hpp index 3e9137c4..1b71ffcd 100644 --- a/include/xo/expression/Expression.hpp +++ b/include/xo/expression/Expression.hpp @@ -5,9 +5,7 @@ #pragma once -#include "xo/reflect/TypeDescr.hpp" -#include "xo/refcnt/Refcounted.hpp" -#include "exprtype.hpp" +#include "GeneralizedExpression.hpp" #include #include @@ -33,7 +31,7 @@ namespace xo { * * Every expression evaluates to a value with a particular type **/ - class Expression : public ref::Refcount { + class Expression : public GeneralizedExpression { public: using VisitFn = std::function )>; @@ -43,10 +41,7 @@ namespace xo { public: explicit Expression(exprtype extype, TypeDescr valuetype) - : extype_{extype}, valuetype_{valuetype}{} - - exprtype extype() const { return extype_; } - TypeDescr valuetype() const { return valuetype_; } + : GeneralizedExpression(extype, valuetype) {} /** find free named variables in this expression. * comprises the set of names that don't match formal parameters in @@ -87,29 +82,8 @@ namespace xo { **/ //virtual std::int32_t find_free_vars(std::vector> env) = 0; - /** write human-readable representation to stream **/ - virtual void display(std::ostream & os) const = 0; - /** human-readable string representation **/ - virtual std::string display_string() const; - - protected: - /** useful when scaffolding expressions in a parser **/ - void assign_valuetype(TypeDescr x) { valuetype_ = x; } - - private: - /** expression type (constant | apply | ..) for this expression **/ - exprtype extype_ = exprtype::invalid; - /** type information (when available) for values produced by this - * expression. - **/ - TypeDescr valuetype_ = nullptr; }; /*Expression*/ - inline std::ostream & - operator<<(std::ostream & os, const Expression & x) { - x.display(os); - return os; - } } /*namespace ast*/ } /*namespace xo*/ diff --git a/include/xo/expression/GeneralizedExpression.hpp b/include/xo/expression/GeneralizedExpression.hpp new file mode 100644 index 00000000..7866284e --- /dev/null +++ b/include/xo/expression/GeneralizedExpression.hpp @@ -0,0 +1,60 @@ +/** @file GeneralizedExpression.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include "exprtype.hpp" +//#include + +namespace xo { + namespace ast { + /** @class GeneralizedExpression + * @brief abstract syntax tree (non-executable) for schematica + * + * 'Generalized' because it includes both kernel and macro expressions. + * Every macro expression automatically translates to an equivalent kernel expression. + * Kernel expressions are directly executable. + **/ + class GeneralizedExpression : public ref::Refcount { + public: + using TypeDescr = xo::reflect::TypeDescr; + + public: + GeneralizedExpression(exprtype extype, TypeDescr valuetype) + : extype_{extype}, valuetype_{valuetype}{} + + exprtype extype() const { return extype_; } + TypeDescr valuetype() const { return valuetype_; } + + /** write human-readable representation to stream **/ + virtual void display(std::ostream & os) const = 0; + /** human-readable string representation **/ + virtual std::string display_string() const; + + protected: + /** useful when scaffolding expressions in a parser **/ + void assign_valuetype(TypeDescr x) { valuetype_ = x; } + + private: + /** expression type (constant | apply | ..) for this expression **/ + exprtype extype_ = exprtype::invalid; + /** type information (when available) for values produced by this + * expression. + **/ + TypeDescr valuetype_ = nullptr; + }; + + inline std::ostream & + operator<<(std::ostream & os, const GeneralizedExpression & x) { + x.display(os); + return os; + } + } /*namespace ast*/ +} /*namespace xo*/ + + +/** end GeneralizedExpression.hpp **/ diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index e5d4a84e..be4d833d 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -86,6 +86,18 @@ namespace xo { virtual void display(std::ostream & os) const override; + protected: + /** create type description for lambda with arguments @p argv + * and body expression @p body + **/ + static TypeDescr assemble_lambda_td(const std::vector> & argv, + const rp & body); + + /** create string description for function signature, + * consistent with c++ expectation + **/ + static std::string assemble_type_str(TypeDescr lambda_td); + private: /** @param lambda_type. function type for this lambda. * We arbitrarily choose the form "Retval(*)(Args...)" @@ -157,6 +169,25 @@ namespace xo { { return Lambda::make(name, argv, body); } + + class LambdaAccess : public Lambda { + public: + static rp make(const std::string & name, + const std::vector> & argv, + const rp & body); + static rp make_empty(); + + private: + /** lambda_type, body can be null here, + * in which case fill in with assign methods + **/ + LambdaAccess(const std::string & name, + TypeDescr lambda_type, + const rp & local_env, + const rp & body); + + + }; } /*namespace ast*/ } /*namespace xo*/ diff --git a/include/xo/expression/Sequence.hpp b/include/xo/expression/Sequence.hpp new file mode 100644 index 00000000..af2fb0e3 --- /dev/null +++ b/include/xo/expression/Sequence.hpp @@ -0,0 +1,52 @@ +/** @file Sequence.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "Expression.hpp" +//#include + +namespace xo { + namespace ast { + class Sequence : public Expression { + public: + Sequence(const std::vector> & xv) + : Expression(exprtype::sequence, + xv[xv.size() - 1]->valuetype()), + expr_v_(xv) {} + + static rp make(const std::vector> & xv) { return new Sequence(xv); } + + std::size_t size() const { return expr_v_.size(); } + const rp & operator[](std::size_t i) const { return expr_v_[i]; } + + // ----- from Expression ----- + + /** note: broken if .expr_v_ contains any def-exprs + * (will treat references to so-defined vars as free). + * must rewrite these first + **/ + virtual std::set get_free_variables() const override; + virtual std::size_t visit_preorder(VisitFn visitor_fn) override; + /** note: borken if .expr_v_ contains any def-exprs **/ + virtual std::size_t visit_layer(VisitFn visitor_fn) override; + virtual rp xform_layer(TransformFn visitor_fn) override; + virtual void attach_envs(ref::brw parent) override; + + // ----- from GeneralizedExpression ---- + + virtual void display(std::ostream & os) const override; + + private: + /** sequence of expressions; evaluate in left-to-right order. + **/ + std::vector> expr_v_; + }; + } /*namespace ast*/ + +} /*namespace xo*/ + + +/** end Sequence.hpp **/ diff --git a/include/xo/expression/exprtype.hpp b/include/xo/expression/exprtype.hpp index 49745d52..ae764021 100644 --- a/include/xo/expression/exprtype.hpp +++ b/include/xo/expression/exprtype.hpp @@ -32,6 +32,8 @@ namespace xo { variable, /** if-then-else **/ ifexpr, + /** sequence **/ + sequence, /** type conversion **/ convert, @@ -51,6 +53,7 @@ namespace xo { case exprtype::lambda: return "lambda"; case exprtype::variable: return "variable"; case exprtype::ifexpr: return "if_expr"; + case exprtype::sequence: return "sequence"; case exprtype::convert: return "convert"; default: break; } diff --git a/src/expression/CMakeLists.txt b/src/expression/CMakeLists.txt index 08c4302f..99a818db 100644 --- a/src/expression/CMakeLists.txt +++ b/src/expression/CMakeLists.txt @@ -2,12 +2,14 @@ set(SELF_LIB xo_expression) set(SELF_SRCS + GeneralizedExpression.cpp Expression.cpp DefineExpr.cpp Apply.cpp Lambda.cpp Variable.cpp IfExpr.cpp + Sequence.cpp LocalEnv.cpp ConvertExpr.cpp Primitive.cpp diff --git a/src/expression/Expression.cpp b/src/expression/Expression.cpp index ecebe8b4..e9f02932 100644 --- a/src/expression/Expression.cpp +++ b/src/expression/Expression.cpp @@ -4,10 +4,6 @@ namespace xo { namespace ast { - std::string - Expression::display_string() const { - return tostr(*this); - } } /*namespace ast*/ } /*namespace xo*/ diff --git a/src/expression/GeneralizedExpression.cpp b/src/expression/GeneralizedExpression.cpp new file mode 100644 index 00000000..6239f700 --- /dev/null +++ b/src/expression/GeneralizedExpression.cpp @@ -0,0 +1,14 @@ +/* @file GeneralizedExpression.cpp */ + +#include "GeneralizedExpression.hpp" + +namespace xo { + namespace ast { + std::string + GeneralizedExpression::display_string() const { + return tostr(*this); + } + } /*namespace ast*/ +} /*namespace xo*/ + +/* end GeneralizedExpression.cpp */ diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index 039e1e1e..98f69e3a 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -5,19 +5,21 @@ #include "xo/reflect/function/FunctionTdx.hpp" #include "xo/indentlog/print/vector.hpp" #include +#include namespace xo { + using xo::reflect::TypeDescr; using xo::reflect::TypeDescrBase; using xo::reflect::FunctionTdxInfo; using std::stringstream; namespace ast { - rp - Lambda::make(const std::string & name, - const std::vector> & argv, - const rp & body) + TypeDescr + Lambda::assemble_lambda_td(const std::vector> & argv, + const rp & body) { - using xo::reflect::FunctionTdx; + if (!body) + return nullptr; /** assemble function type. * @@ -41,8 +43,39 @@ namespace xo { TypeDescr lambda_td = TypeDescrBase::require_by_fn_info(function_info); + return lambda_td; + } + + std::string + Lambda::assemble_type_str(TypeDescr lambda_td) { + assert(lambda_td); + + std::stringstream ss; + + ss << lambda_td->fn_retval()->short_name() + << "("; + + for (std::size_t i = 0, n = lambda_td->n_fn_arg(); i < n; ++i) { + if (i > 0) + ss << ","; + ss << lambda_td->fn_arg(i)->short_name(); + } + ss << ")"; + + return ss.str(); + } + + rp + Lambda::make(const std::string & name, + const std::vector> & argv, + const rp & body) + { + using xo::reflect::FunctionTdx; + rp env = LocalEnv::make(argv); + TypeDescr lambda_td = assemble_lambda_td(argv, body); + rp retval = new Lambda(name, lambda_td, @@ -122,14 +155,15 @@ namespace xo { } /*regularize_layer_vars*/ Lambda::Lambda(const std::string & name, - TypeDescr lambda_type, + TypeDescr lambda_td, const rp & local_env, const rp & body) - : FunctionInterface(exprtype::lambda, lambda_type), + : FunctionInterface(exprtype::lambda, lambda_td), name_{name}, body_{body}, local_env_{local_env} { +#ifdef OBSOLETE stringstream ss; ss << "double"; ss << "("; @@ -139,8 +173,10 @@ namespace xo { ss << "double"; } ss << ")"; +#endif - this->type_str_ = ss.str(); + if (lambda_td) + this->type_str_ = assemble_type_str(lambda_td); /* ensure variables are unique within layer for this lambda */ this->layer_var_map_ = this->regularize_layer_vars(); @@ -209,6 +245,28 @@ namespace xo { << xtag("body", body_) << ">"; } /*display*/ + + // ----- Lambda Access ----- + + rp + LambdaAccess::make(const std::string & name, + const std::vector> & argv, + const rp & body) + { + TypeDescr lambda_td = + + TypeDescr body_valuetype = nullptr; + + } + + rp + LambdaAccess::make_empty() + { + return new LambdaAccess("" /*name*/, + nullptr /*lambda_td*/, + nullptr /*local_env*/, + nullptr /*body*/); + } } /*namespace ast*/ } /*namespace xo*/ diff --git a/src/expression/Sequence.cpp b/src/expression/Sequence.cpp new file mode 100644 index 00000000..98269e2c --- /dev/null +++ b/src/expression/Sequence.cpp @@ -0,0 +1,78 @@ +/* @file Sequence.cpp */ + +#include "Sequence.hpp" +#include + +namespace xo { + namespace ast { + std::set + Sequence::get_free_variables() const { + std::set retval; + + for (const auto & x : expr_v_) { + std::set free_vars; + free_vars = x->get_free_variables(); + + for (const auto & y : free_vars) + retval.insert(y); + } + + return retval; + } + + std::size_t + Sequence::visit_preorder(VisitFn visitor_fn) { + std::size_t n = 1; + + visitor_fn(this); + + for (const auto & x : expr_v_) + n += x->visit_preorder(visitor_fn); + + return n; + } + + std::size_t + Sequence::visit_layer(VisitFn visitor_fn) { + std::size_t n = 1; + + visitor_fn(this); + + for (const auto & x : expr_v_) + n += x->visit_layer(visitor_fn); + + return n; + } + + rp + Sequence::xform_layer(TransformFn xform_fn) { + for (std::size_t i = 0, n = expr_v_.size(); i < n; ++i) { + expr_v_[i] = expr_v_[i]->xform_layer(xform_fn); + } + + return xform_fn(this); + } + + void + Sequence::attach_envs(ref::brw p) { + for (const auto & x : expr_v_) + x->attach_envs(p); + } + + void + Sequence::display(std::ostream & os) const { + os << ""; + } + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end Sequence.cpp */ From dad6b2562c5d485851094287289c4b844ced7359 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 20 Aug 2024 13:32:08 -0400 Subject: [PATCH 1455/2524] xo-reader: + sequence expression (aka beginexpr or block) --- include/xo/reader/expect_expr_xs.hpp | 29 ++++++++++- include/xo/reader/exprstate.hpp | 13 +++++ include/xo/reader/sequence_xs.hpp | 34 ++++++++++++ src/reader/CMakeLists.txt | 1 + src/reader/expect_expr_xs.cpp | 69 +++++++++++++++++++++--- src/reader/exprstate.cpp | 26 ++++++++++ src/reader/sequence_xs.cpp | 78 ++++++++++++++++++++++++++++ 7 files changed, 241 insertions(+), 9 deletions(-) create mode 100644 include/xo/reader/sequence_xs.hpp create mode 100644 src/reader/sequence_xs.cpp diff --git a/include/xo/reader/expect_expr_xs.hpp b/include/xo/reader/expect_expr_xs.hpp index 650a3195..c3e8a513 100644 --- a/include/xo/reader/expect_expr_xs.hpp +++ b/include/xo/reader/expect_expr_xs.hpp @@ -15,16 +15,29 @@ namespace xo { **/ class expect_expr_xs : public exprstate { public: - expect_expr_xs(); + explicit expect_expr_xs(bool allow_defs, + bool cxl_on_rightbrace); static void start(parserstatemachine * p_psm); + static void start(bool allow_defs, + bool cxl_on_rightbrace, + parserstatemachine * p_psm); virtual void on_lambda_token(const token_type & tk, parserstatemachine * p_psm) override; + virtual void on_def_token(const token_type & tk, + parserstatemachine * p_psm) override; + virtual void on_leftparen_token(const token_type & tk, parserstatemachine * p_psm) override; + virtual void on_leftbrace_token(const token_type & tk, + parserstatemachine * p_psm) override; + + virtual void on_rightbrace_token(const token_type & tk, + parserstatemachine * p_psm) override; + virtual void on_symbol_token(const token_type & tk, parserstatemachine * p_psm) override; @@ -36,7 +49,19 @@ namespace xo { parserstatemachine * p_psm) override; private: - static std::unique_ptr make(); + static std::unique_ptr make(bool allow_defs, + bool cxl_on_rightbrace); + + private: + /* if true: allow a define-expression here */ + bool allow_defs_ = false; + /* if true: expecting either: + * - expression + * - right brace '}', in which case no expression + * if false: expecting + * - expression + */ + bool cxl_on_rightbrace_ = false; }; } /*namespace scm*/ diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 3e79dee4..b22bf85f 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -33,6 +33,11 @@ namespace xo { **/ parenexpr, + /** handle sequence expression (aka block) + * see @ref sequence_xs + **/ + sequenceexpr, + expect_rhs_expression, expect_symbol, expect_type, @@ -146,6 +151,14 @@ namespace xo { virtual void on_rightparen_token(const token_type & tk, parserstatemachine * p_psm); + /** handle incoming '{' token **/ + virtual void on_leftbrace_token(const token_type & tk, + parserstatemachine * p_psm); + + /** handle incoming '}' token **/ + virtual void on_rightbrace_token(const token_type & tk, + parserstatemachine * p_psm); + /** handle incoming operator token **/ virtual void on_operator_token(const token_type & tk, parserstatemachine * p_psm); diff --git a/include/xo/reader/sequence_xs.hpp b/include/xo/reader/sequence_xs.hpp new file mode 100644 index 00000000..a33cf3e1 --- /dev/null +++ b/include/xo/reader/sequence_xs.hpp @@ -0,0 +1,34 @@ +/** @file sequence_xs.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "exprstate.hpp" + +namespace xo { + namespace scm { + class sequence_xs : public exprstate { + public: + sequence_xs(); + + static void start(parserstatemachine * p_psm); + + virtual void on_expr(ref::brw expr, + parserstatemachine * p_psm) override; + + virtual void on_rightbrace_token(const token_type & tk, + parserstatemachine * p_psm) override; + + private: + static std::unique_ptr make(); + + private: + std::vector> expr_v_; + }; + } /*namespace scm*/ +} /*namespace xo*/ + + +/** end sequence_xs.hpp **/ diff --git a/src/reader/CMakeLists.txt b/src/reader/CMakeLists.txt index 83dc675d..5db87d7a 100644 --- a/src/reader/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -10,6 +10,7 @@ set(SELF_SRCS define_xs.cpp progress_xs.cpp paren_xs.cpp + sequence_xs.cpp exprseq_xs.cpp expect_expr_xs.cpp expect_symbol_xs.cpp diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index 9896421e..c07c5d42 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -7,7 +7,9 @@ #include "parserstatemachine.hpp" #include "exprstatestack.hpp" #include "lambda_xs.hpp" +#include "define_xs.hpp" #include "paren_xs.hpp" +#include "sequence_xs.hpp" #include "progress_xs.hpp" #include "xo/expression/Lambda.hpp" #include "xo/expression/Constant.hpp" @@ -18,20 +20,46 @@ namespace xo { namespace scm { std::unique_ptr - expect_expr_xs::make() { - return std::make_unique(expect_expr_xs()); + expect_expr_xs::make(bool allow_defs, + bool cxl_on_rightbrace) + { + return std::make_unique(expect_expr_xs(allow_defs, + cxl_on_rightbrace)); } void - expect_expr_xs::start(parserstatemachine * p_psm) { - p_psm->push_exprstate(expect_expr_xs::make()); + expect_expr_xs::start(bool allow_defs, + bool cxl_on_rightbrace, + parserstatemachine * p_psm) { + p_psm->push_exprstate(expect_expr_xs::make(allow_defs, + cxl_on_rightbrace)); } - expect_expr_xs::expect_expr_xs() - : exprstate(exprstatetype::expect_rhs_expression) + void + expect_expr_xs::start(parserstatemachine * p_psm) { + start(false /*!allow_defs*/, + false /*!cxl_on_rightbrace*/, + p_psm); + } + + expect_expr_xs::expect_expr_xs(bool allow_defs, + bool cxl_on_rightbrace) + : exprstate(exprstatetype::expect_rhs_expression), + allow_defs_{allow_defs}, + cxl_on_rightbrace_{cxl_on_rightbrace} {} + void + expect_expr_xs::on_def_token(const token_type & tk, + parserstatemachine * p_psm) + { + if (allow_defs_) + define_xs::start(p_psm); + else + exprstate::on_def_token(tk, p_psm); + } + void expect_expr_xs::on_lambda_token(const token_type & /*tk*/, parserstatemachine * p_psm) @@ -41,7 +69,6 @@ namespace xo { //constexpr const char * self_name = "exprstate::on_leftparen"; - /* push lparen_0 to remember to look for subsequent rightparen. */ lambda_xs::start(p_psm); } @@ -58,6 +85,34 @@ namespace xo { paren_xs::start(p_psm); } + void + expect_expr_xs::on_leftbrace_token(const token_type & /*tk*/, + parserstatemachine * p_psm) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + //constexpr const char * self_name = "exprstate::on_leftparen"; + + /* push lparen_0 to remember to look for subsequent rightparen. */ + sequence_xs::start(p_psm); + } + + void + expect_expr_xs::on_rightbrace_token(const token_type & tk, + parserstatemachine * p_psm) + { + if (cxl_on_rightbrace_) { + auto self = p_psm->pop_exprstate(); + + /* do not call .on_expr(), since '}' cancelled */ + + p_psm->top_exprstate().on_rightbrace_token(tk, p_psm); + } else { + exprstate::on_rightbrace_token(tk, p_psm); + } + } + void expect_expr_xs::on_symbol_token(const token_type & tk, parserstatemachine * p_psm) diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 9721ede2..dfb490ea 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -33,6 +33,8 @@ namespace xo { return "lambdaexpr"; case exprstatetype::parenexpr: return "parenexpr"; + case exprstatetype::sequenceexpr: + return "sequenceexpr"; case exprstatetype::expect_rhs_expression: return "expect_rhs_expression"; case exprstatetype::expect_symbol: @@ -211,6 +213,30 @@ namespace xo { this->illegal_input_error(self_name, tk); } + void + exprstate::on_leftbrace_token(const token_type & tk, + parserstatemachine * /*p_psm*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_leftbrace_token"; + + this->illegal_input_error(self_name, tk); + } + + void + exprstate::on_rightbrace_token(const token_type & tk, + parserstatemachine * /*p_psm*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + constexpr const char * self_name = "exprstate::on_rightbrace_token"; + + this->illegal_input_error(self_name, tk); + } + void exprstate::on_operator_token(const token_type & tk, parserstatemachine * /*p_psm*/) diff --git a/src/reader/sequence_xs.cpp b/src/reader/sequence_xs.cpp new file mode 100644 index 00000000..68694e53 --- /dev/null +++ b/src/reader/sequence_xs.cpp @@ -0,0 +1,78 @@ +/* @file sequence_xs.cpp */ + +#include "sequence_xs.hpp" +#include "parserstatemachine.hpp" +#include "expect_expr_xs.hpp" +#include "xo/expression/Sequence.hpp" + +namespace xo { + using xo::ast::Sequence; + + namespace scm { + std::unique_ptr + sequence_xs::make() { + return std::make_unique(sequence_xs()); + } + + void + sequence_xs::start(parserstatemachine * p_psm) { + p_psm->push_exprstate(sequence_xs::make()); + /* want to accept anything that starts an expression, + * except that } ends it + */ + expect_expr_xs::start(true /*allow_defs*/, + true /*cxl_on_rightbrace*/, + p_psm); + } + + sequence_xs::sequence_xs() + : exprstate(exprstatetype::sequenceexpr) + {} + + void + sequence_xs::on_expr(ref::brw expr, + parserstatemachine * p_psm) + { + /* TODO: if expr is a DefineExpr, + * then need to rewrite... + * + * ...prefix + * DefineExpr(lhs_name, rhs) + * rest... + * + * becomes: + * + * Sequence( + * ...prefix, + * Apply(Lambda(gen999, [Variable(lhs_name, type(rhs))], sequencify(rest...)), + * rhs)) + * + * so amongst other things, + * helpful to have nested seequence_xs that propagates '}' instead of swallowing it. + */ + + this->expr_v_.push_back(expr.promote()); + expect_expr_xs::start(true /*allow_defs*/, + true /*cxl_on_rightbrace*/, + p_psm); + } + + void + sequence_xs::on_rightbrace_token(const token_type & /*tk*/, + parserstatemachine * p_psm) + { + auto self = p_psm->pop_exprstate(); + + /* make sequence from expressions seen at this level, + * and report it to parent + */ + auto expr = Sequence::make(this->expr_v_); + + p_psm->top_exprstate().on_expr(expr, p_psm); + } + + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end sequence_xs.cpp */ From a0e921e9ee5a4e510d107fd64f89e5956664986a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 21 Aug 2024 14:35:21 -0400 Subject: [PATCH 1456/2524] xo-expression: LambdaAccess [wip] --- include/xo/expression/Lambda.hpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index be4d833d..7ba7cdd2 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -98,7 +98,6 @@ namespace xo { **/ static std::string assemble_type_str(TypeDescr lambda_td); - private: /** @param lambda_type. function type for this lambda. * We arbitrarily choose the form "Retval(*)(Args...)" **/ @@ -107,6 +106,7 @@ namespace xo { const rp & local_env, const rp & body); + protected: /** compute free-variable set for this lambda **/ std::set calc_free_variables() const; @@ -118,17 +118,17 @@ namespace xo { **/ std::map> regularize_layer_vars(); - private: /** lambda name. Initially supporting only form like * (define (foo x y z) * (+ (* x x) (* y y) (* z z))) * - * In any case need to supply names for distinct things-for-which-code-is-generated - * so that they can be linked etc. + * In any case need to supply names for distinct + * things-for-which-code-is-generated so that they can be linked etc. **/ std::string name_; /** e.g. - * "double(double,double)" for function of two doubles that returns a double + * "double(double,double)" for function of two doubles that + * returns a double **/ std::string type_str_; /** function body **/ @@ -177,6 +177,11 @@ namespace xo { const rp & body); static rp make_empty(); + /* TODO: make sure Lambda members that depend on non-emtpy body + * get calc'd + */ + void assign_body(const rp & body); + private: /** lambda_type, body can be null here, * in which case fill in with assign methods From 3e28f8b42c1521d25225c089c3ae0b59823bffa0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 21 Aug 2024 14:35:50 -0400 Subject: [PATCH 1457/2524] missed .cpp --- src/expression/Lambda.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index 98f69e3a..c21df94b 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -253,10 +253,19 @@ namespace xo { const std::vector> & argv, const rp & body) { - TypeDescr lambda_td = + TypeDescr lambda_td = assemble_lambda_td(argv, body); + rp env = LocalEnv::make(argv); - TypeDescr body_valuetype = nullptr; + rp retval + = new LambdaAccess(name, + lambda_td, + env, + body); + /* need two-phase construction b/c pointer cycle */ + env->assign_origin(retval.get()); + + return retval; } rp @@ -267,6 +276,14 @@ namespace xo { nullptr /*local_env*/, nullptr /*body*/); } + + LambdaAccess::LambdaAccess(const std::string & name, + TypeDescr lambda_td, + const rp & local_env, + const rp & body) + : Lambda(name, lambda_td, local_env, body) + {} + } /*namespace ast*/ } /*namespace xo*/ From b906fdfa5ac51d679240cd95a28860efe8732f12 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 22 Aug 2024 15:45:31 -0400 Subject: [PATCH 1458/2524] xo-expression: + Lambda.complete_assembly_from_body() --- include/xo/expression/Lambda.hpp | 15 +++++-- src/expression/Lambda.cpp | 72 +++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 5 deletions(-) diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index 7ba7cdd2..668d1303 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -106,7 +106,6 @@ namespace xo { const rp & local_env, const rp & body); - protected: /** compute free-variable set for this lambda **/ std::set calc_free_variables() const; @@ -118,6 +117,14 @@ namespace xo { **/ std::map> regularize_layer_vars(); + /** compute derived members + * (type_str_, free_var_set_, captured_var_set_, layer_var_map_, + * nested_lambda_map_) + * once .body_ is established + **/ + void complete_assembly_from_body(); + + protected: /** lambda name. Initially supporting only form like * (define (foo x y z) * (+ (* x x) (* y y) (* z z))) @@ -177,9 +184,9 @@ namespace xo { const rp & body); static rp make_empty(); - /* TODO: make sure Lambda members that depend on non-emtpy body - * get calc'd - */ + /** assign body + compute derived members + * (see complete_assembly_from_body()) + **/ void assign_body(const rp & body); private: diff --git a/src/expression/Lambda.cpp b/src/expression/Lambda.cpp index c21df94b..5eae7973 100644 --- a/src/expression/Lambda.cpp +++ b/src/expression/Lambda.cpp @@ -154,6 +154,68 @@ namespace xo { return var_map; } /*regularize_layer_vars*/ + void + Lambda::complete_assembly_from_body() { + if (body_) { + TypeDescr lambda_td + = assemble_lambda_td(this->local_env_->argv(), body_); + + if (lambda_td) + this->type_str_ = assemble_type_str(lambda_td); + + this->layer_var_map_ = this->regularize_layer_vars(); + + this->free_var_set_ = this->calc_free_variables(); + + std::map> nested_lambda_map; + { + this->body_->visit_layer + ([&nested_lambda_map] + (ref::brw expr) + { + if (expr->extype() == exprtype::lambda) { + ref::brw lm = Lambda::from(expr); + + nested_lambda_map[lm->name()] = lm.get(); + } + }); + } + this->nested_lambda_map_ = std::move(nested_lambda_map); + + /* establish the set of captured local vars. + * These are any formal parameters that appear free in + * any layer of a nested lambda. + */ + std::set captured_var_set; + { + for (const auto & ix : nested_lambda_map_) { + std::set nested_free_var_set + = ix.second->get_free_variables(); + + for (const auto & jx : nested_free_var_set) { + /* check whether variable *jx is one of this lambda's + * formals + */ + auto bind = this->local_env_->lookup_local_binding(jx); + + if (bind.i_link_ == 0) { + /* yup, it's a formal parameter of this lambda */ + captured_var_set.insert(jx); + } + } + } + } + + this->captured_var_set_ = std::move(captured_var_set); + + /* in particular: + * - establish binding path (intrusively) for each variable + * assigns Variable::path_ + */ + this->body_->attach_envs(local_env_); + } + } + Lambda::Lambda(const std::string & name, TypeDescr lambda_td, const rp & local_env, @@ -209,7 +271,9 @@ namespace xo { = ix.second->get_free_variables(); for (const auto & jx : nested_free_var_set) { - /* check whether variable *jx is one of this lambda's formals */ + /* check whether variable *jx is one of this lambda's + * formals + */ auto bind = this->local_env_->lookup_local_binding(jx); if (bind.i_link_ == 0) { @@ -284,6 +348,12 @@ namespace xo { : Lambda(name, lambda_td, local_env, body) {} + void + LambdaAccess::assign_body(const rp & body) { + this->body_ = body; + + this->complete_assembly_from_body(); + } } /*namespace ast*/ } /*namespace xo*/ From 7fad60290d9e8587f94a1aa234c275693bb0c08a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 23 Aug 2024 10:52:51 -0400 Subject: [PATCH 1459/2524] xo-reader: handle sequence with embedded local vars --- include/xo/reader/let1_xs.hpp | 57 +++++++++++++++ include/xo/reader/sequence_xs.hpp | 15 +++- src/reader/CMakeLists.txt | 1 + src/reader/let1_xs.cpp | 116 ++++++++++++++++++++++++++++++ src/reader/sequence_xs.cpp | 44 ++++++++++-- 5 files changed, 225 insertions(+), 8 deletions(-) create mode 100644 include/xo/reader/let1_xs.hpp create mode 100644 src/reader/let1_xs.cpp diff --git a/include/xo/reader/let1_xs.hpp b/include/xo/reader/let1_xs.hpp new file mode 100644 index 00000000..4486869f --- /dev/null +++ b/include/xo/reader/let1_xs.hpp @@ -0,0 +1,57 @@ +/* file let1_xs.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "exprstate.hpp" + +namespace xo { + namespace scm { + class let1_xs : public exprstate { + public: + + /** given local definition equivalent to + * def lhs_name = rhs + * rest... + * parse sequence of incoming expressions rest... (until '}') + * + * Result expression creates and inits @p lhs_name, + * then evaluates expressions that follow definition + * up to same-level '}' + **/ + static void start(const std::string & lhs_name, + const rp & rhs, + parserstatemachine * p_psm); + + virtual void on_expr(ref::brw expr, + parserstatemachine * p_psm) override; + + virtual void on_rightbrace_token(const token_type & tk, + parserstatemachine * p_psm) override; + + private: + let1_xs(std::string lhs_name, + rp rhs); + + /** named ctor idiom **/ + static std::unique_ptr make(std::string lhs_name, + rp rhs); + + private: + /** name for new local variable **/ + std::string lhs_name_; + /** set initial value for @ref lhs_name_ from value of this expression **/ + rp rhs_; + + /** evaluate expressions in this sequence, in order, in environment + * with variable @ref lhs_name_ defined + **/ + std::vector> expr_v_; + }; + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end let1_xs.hpp */ diff --git a/include/xo/reader/sequence_xs.hpp b/include/xo/reader/sequence_xs.hpp index a33cf3e1..b361bbf7 100644 --- a/include/xo/reader/sequence_xs.hpp +++ b/include/xo/reader/sequence_xs.hpp @@ -6,13 +6,22 @@ #pragma once #include "exprstate.hpp" +#include namespace xo { + namespace ast { class Sequence; } + namespace ast { class Lambda; } + namespace scm { class sequence_xs : public exprstate { public: - sequence_xs(); + using Sequence = xo::ast::Sequence; + using Lambda = xo::ast::Lambda; + public: + /** start parsing a sequence-expr. + * input begins with first expression in the sequence. + **/ static void start(parserstatemachine * p_psm); virtual void on_expr(ref::brw expr, @@ -22,9 +31,13 @@ namespace xo { parserstatemachine * p_psm) override; private: + sequence_xs(); + + /** named ctor idiom **/ static std::unique_ptr make(); private: + /** will build SequenceExpr from in-order contents of this vector **/ std::vector> expr_v_; }; } /*namespace scm*/ diff --git a/src/reader/CMakeLists.txt b/src/reader/CMakeLists.txt index 5db87d7a..0a3dee3e 100644 --- a/src/reader/CMakeLists.txt +++ b/src/reader/CMakeLists.txt @@ -18,6 +18,7 @@ set(SELF_SRCS expect_formal_arglist_xs.cpp expect_type_xs.cpp lambda_xs.cpp + let1_xs.cpp envframestack.cpp envframe.cpp) diff --git a/src/reader/let1_xs.cpp b/src/reader/let1_xs.cpp new file mode 100644 index 00000000..f248d1dd --- /dev/null +++ b/src/reader/let1_xs.cpp @@ -0,0 +1,116 @@ +/* file let1_xs.cpp + * + * author: Roland Conybeare + */ + +#include "let1_xs.hpp" +#include "expect_expr_xs.hpp" +#include "parserstatemachine.hpp" +#include "xo/expression/Sequence.hpp" +#include "xo/expression/DefineExpr.hpp" +#include "xo/expression/Apply.hpp" +#include "xo/expression/Lambda.hpp" + +namespace xo { + using Sequence = xo::ast::Sequence; + using DefineExpr = xo::ast::DefineExpr; + using Apply = xo::ast::Apply; + using Lambda = xo::ast::Lambda; + using LambdaAccess = xo::ast::LambdaAccess; + using Variable = xo::ast::Variable; + + namespace { + std::string gensym() { + return "genanotherxx"; + } + } + + namespace scm { + std::unique_ptr + let1_xs::make(std::string lhs_name, + rp rhs) + { + return std::make_unique(let1_xs(std::move(lhs_name), + std::move(rhs))); + } + + void + let1_xs::start(const std::string & lhs_name, + const rp & rhs, + parserstatemachine * p_psm) + { + p_psm->push_exprstate(let1_xs::make(std::move(lhs_name), + std::move(rhs))); + + expect_expr_xs::start(true /*allow_defs*/, + true /*cxl_on_rightbrace*/, + p_psm); + } + + let1_xs::let1_xs(std::string lhs_name, + rp rhs) + : lhs_name_{std::move(lhs_name)}, + rhs_{std::move(rhs)} + {} + + void + let1_xs::on_expr(ref::brw expr, + parserstatemachine * p_psm) + { + ref::brw def_expr = DefineExpr::from(expr); + + if (def_expr) { + /** nested_start: control returns via + * .on_expr(x) + * with x something like: + * Apply(Lambda(gensym(), + * [Variable(def_expr->lhs_name(), + * def_expr->valuetype())], + * body...)) + * followed immediately by + * .on_rightbrace_token() + **/ + let1_xs::start(def_expr->lhs_name(), + def_expr->rhs(), + p_psm); + } else { + this->expr_v_.push_back(expr.promote()); + + expect_expr_xs::start(true /*allow_defs*/, + true /*cxl_on_rightbrace*/, + p_psm); + } + } + + void + let1_xs::on_rightbrace_token(const token_type & tk, + parserstatemachine * p_psm) + { + auto self = p_psm->pop_exprstate(); + + auto expr = Sequence::make(this->expr_v_); + + std::string argname = gensym(); + + rp lambda + = Lambda::make(this->lhs_name_, + {Variable::make(argname, + this->rhs_->valuetype())}, + expr); + + rp result + = Apply::make(lambda, {this->rhs_}); + + p_psm->top_exprstate().on_expr(result, p_psm); + + /* caller of let1_xs expects the same rightbrace '}' + * -- remember we pushed let1_xs to handle an embedded def-expr + * in a sequence + */ + p_psm->top_exprstate().on_rightbrace_token(tk, p_psm); + } + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end let1_xs.cpp */ diff --git a/src/reader/sequence_xs.cpp b/src/reader/sequence_xs.cpp index 68694e53..c9ee71f3 100644 --- a/src/reader/sequence_xs.cpp +++ b/src/reader/sequence_xs.cpp @@ -3,10 +3,12 @@ #include "sequence_xs.hpp" #include "parserstatemachine.hpp" #include "expect_expr_xs.hpp" +#include "let1_xs.hpp" +#include "xo/expression/DefineExpr.hpp" #include "xo/expression/Sequence.hpp" namespace xo { - using xo::ast::Sequence; + using xo::ast::DefineExpr; namespace scm { std::unique_ptr @@ -42,19 +44,47 @@ namespace xo { * * becomes: * + * /-- .outer_seq_expr_ + * v * Sequence( * ...prefix, - * Apply(Lambda(gen999, [Variable(lhs_name, type(rhs))], sequencify(rest...)), + * + * /-- .inner_lm_expr_ + * v + * Apply(Lambda(gen999, + * [Variable(lhs_name, type(rhs))], + * /-- .expr_v_ + * v + * sequencify(rest...)), * rhs)) * * so amongst other things, - * helpful to have nested seequence_xs that propagates '}' instead of swallowing it. + * helpful to have nested seequence_xs that propagates '}' + * instead of swallowing it. */ + ref::brw def_expr = DefineExpr::from(expr); - this->expr_v_.push_back(expr.promote()); - expect_expr_xs::start(true /*allow_defs*/, - true /*cxl_on_rightbrace*/, - p_psm); + if (def_expr) { + /** nested_start: control returns via + * .on_expr(x) + * with x something like: + * Apply(Lambda(gensym(), + * [Variable(def_expr->lhs_name(), + * def_expr->valuetype())], + * body...)) + * followed immediately by + * .on_rightbrace_token() + **/ + let1_xs::start(def_expr->lhs_name(), + def_expr->rhs(), + p_psm); + } else { + this->expr_v_.push_back(expr.promote()); + + expect_expr_xs::start(true /*allow_defs*/, + true /*cxl_on_rightbrace*/, + p_psm); + } } void From d0b28e3cd4a4ecf15f85a95d720ac978917fa20c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 23 Aug 2024 11:35:10 -0400 Subject: [PATCH 1460/2524] xo-expression: + AssignExpr --- include/xo/expression/AssignExpr.hpp | 61 ++++++++++++++++++ include/xo/expression/exprtype.hpp | 3 + src/expression/AssignExpr.cpp | 93 ++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 include/xo/expression/AssignExpr.hpp create mode 100644 src/expression/AssignExpr.cpp diff --git a/include/xo/expression/AssignExpr.hpp b/include/xo/expression/AssignExpr.hpp new file mode 100644 index 00000000..0e7d5e5a --- /dev/null +++ b/include/xo/expression/AssignExpr.hpp @@ -0,0 +1,61 @@ +/* file AssignExpr.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "Expression.hpp" +#include "Variable.hpp" + +namespace xo { + namespace ast { + /** @class AssignExpr + * @brief Provide expression for assigning to a variable + * + * def pi = 3.14159265; + * def foo = 0.0; + * foo := pi / 2; + **/ + class AssignExpr : public Expression { + public: + static rp make(const rp & lhs, + const rp & rhs); + + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + const rp & lhs() const { return lhs_; } + const rp & rhs() const { return rhs_; } + + std::set calc_free_variables() const; + + // ----- inherited from Expression ----- + + virtual std::set get_free_variables() const override; + virtual std::size_t visit_preorder(VisitFn visitor_fn) override; + virtual std::size_t visit_layer(VisitFn visitor_fn) override; + virtual rp xform_layer(TransformFn xform_fn) override; + virtual void attach_envs(ref::brw p) override; + + virtual void display(std::ostream & os) const override; + + private: + AssignExpr(const rp & lhs, + const rp & rhs); + + private: + /** assign to this variable. **/ + rp lhs_; + /** assign value of this expression to variable @p lhs **/ + rp rhs_; + + /** free variables for this assignment **/ + std::set free_var_set_; + }; + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end AssignExpr.hpp */ diff --git a/include/xo/expression/exprtype.hpp b/include/xo/expression/exprtype.hpp index ae764021..e9b8393c 100644 --- a/include/xo/expression/exprtype.hpp +++ b/include/xo/expression/exprtype.hpp @@ -24,6 +24,8 @@ namespace xo { primitive, /** variable/function definition **/ define, + /** variable assignment **/ + assign, /** function call **/ apply, /** function definition **/ @@ -49,6 +51,7 @@ namespace xo { case exprtype::constant: return "constant"; case exprtype::primitive: return "primitive"; case exprtype::define: return "define"; + case exprtype::assign: return "assign"; case exprtype::apply: return "apply"; case exprtype::lambda: return "lambda"; case exprtype::variable: return "variable"; diff --git a/src/expression/AssignExpr.cpp b/src/expression/AssignExpr.cpp new file mode 100644 index 00000000..8d56a058 --- /dev/null +++ b/src/expression/AssignExpr.cpp @@ -0,0 +1,93 @@ +/* file AssignExpr.cpp + * + * author: Roland Conybeare + */ + +#include "AssignExpr.hpp" +#include "xo/indentlog/print/tag.hpp" + +namespace xo { + namespace ast { + rp + AssignExpr::make(const rp & lhs, + const rp & rhs) + { + return new AssignExpr(lhs, rhs); + } + + AssignExpr::AssignExpr(const rp & lhs, + const rp & rhs) + : Expression(exprtype::assign, rhs->valuetype()), + lhs_{lhs}, rhs_{rhs} + { + this->free_var_set_ = this->calc_free_variables(); + } + + std::set + AssignExpr::calc_free_variables() const + { + std::set retval = lhs_->get_free_variables(); + + std::set tmp = rhs_->get_free_variables(); + + for (const auto & name : tmp) + retval.insert(name); + + return retval; + } + + std::set + AssignExpr::get_free_variables() const { + return free_var_set_; + } + + std::size_t + AssignExpr::visit_preorder(VisitFn visitor_fn) { + std::size_t n = 1; + + visitor_fn(this); + + n += lhs_->visit_preorder(visitor_fn); + n += rhs_->visit_preorder(visitor_fn); + + return n; + } + + std::size_t + AssignExpr::visit_layer(VisitFn visitor_fn) { + std::size_t n = 1; + + visitor_fn(this); + + n += lhs_->visit_layer(visitor_fn); + n += rhs_->visit_layer(visitor_fn); + + return n; + } + + rp + AssignExpr::xform_layer(TransformFn xform_fn) { + this->lhs_ = Variable::from(lhs_->xform_layer(xform_fn)).promote(); + this->rhs_ = rhs_->xform_layer(xform_fn); + + return xform_fn(this); + } + + void + AssignExpr::attach_envs(ref::brw p) { + lhs_->attach_envs(p); + rhs_->attach_envs(p); + } + + void + AssignExpr::display(std::ostream & os) const { + os << ""; + } + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end AssignExpr.cpp */ From 47a5e7ccaa3fd7907c60d2b65a442846eba2b2b3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 23 Aug 2024 11:35:45 -0400 Subject: [PATCH 1461/2524] xo-expression: build: + AssignExpr --- src/expression/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/expression/CMakeLists.txt b/src/expression/CMakeLists.txt index 99a818db..0241ec67 100644 --- a/src/expression/CMakeLists.txt +++ b/src/expression/CMakeLists.txt @@ -5,6 +5,7 @@ set(SELF_SRCS GeneralizedExpression.cpp Expression.cpp DefineExpr.cpp + AssignExpr.cpp Apply.cpp Lambda.cpp Variable.cpp From bff6b7ce9bf752e12069d9f59cd359c466bf194a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 24 Aug 2024 12:30:22 -0400 Subject: [PATCH 1462/2524] xo-tokenizer: = and := tokens --- include/xo/tokenizer/token.hpp | 2 +- include/xo/tokenizer/tokenizer.hpp | 79 +++++++++++++++++++++++++----- utest/tokenizer.test.cpp | 75 ++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 12 deletions(-) diff --git a/include/xo/tokenizer/token.hpp b/include/xo/tokenizer/token.hpp index 84b1a1b8..9944cb3d 100644 --- a/include/xo/tokenizer/token.hpp +++ b/include/xo/tokenizer/token.hpp @@ -77,7 +77,7 @@ namespace xo { static token doublecolon() { return token(tokentype::tk_doublecolon); } static token semicolon() { return token(tokentype::tk_semicolon); } static token singleassign() { return token(tokentype::tk_singleassign); } - static token assign() { return token(tokentype::tk_assign); } + static token assign_token() { return token(tokentype::tk_assign); } static token yields() { return token(tokentype::tk_yields); } static token type() { return token(tokentype::tk_type); } diff --git a/include/xo/tokenizer/tokenizer.hpp b/include/xo/tokenizer/tokenizer.hpp index 11ee5aca..bb67eb13 100644 --- a/include/xo/tokenizer/tokenizer.hpp +++ b/include/xo/tokenizer/tokenizer.hpp @@ -61,10 +61,16 @@ namespace xo { /** identifies punctuation chars. * These are chars that are not permitted to appear within - * a string/symbol token. Instead they force completion of + * a symbol token. Instead they force completion of * a preceding token, and start a new token with themselves **/ - bool is_punctuation(CharT ch) const; + bool is_1char_punctuation(CharT ch) const; + + /** more-relazed version of is_1char_punctuation. + * Chars that are not permitted to appear within a symbol token, + * but may form token combined with next character + **/ + bool is_2char_punctuation(CharT ch) const; /** true if tokenizer contains stored prefix of * possibly-incomplete token @@ -115,7 +121,7 @@ namespace xo { template bool - tokenizer::is_punctuation(CharT ch) const { + tokenizer::is_1char_punctuation(CharT ch) const { switch(ch) { case '<': return true; @@ -138,23 +144,36 @@ namespace xo { case ';': return true; case ':': - return true; + /* can't be 1char punctuation -- can begin assignment token */ + return false; case '=': return true; case '-': - /* can't be punctuation -- can appear inside f64 token */ + /* can't be punctuation -- can appear inside f64 token: e.g. 1.23e-9 */ return false; case '+': - /* can't be punctuation -- can appear inside f64 token */ + /* can't be punctuation -- can appear inside f64 token: e.g. 1.23e+4 */ return false; case '.': - /* can't be punctuation -- can appear inside f64 token */ + /* can't be punctuation -- can appear inside f64 token: e.g. 1.23 */ return false; } return false; } + template + bool + tokenizer::is_2char_punctuation(CharT ch) const { + switch(ch) { + case ':': + /* can begin := */ + return true; + } + + return false; + } + template auto tokenizer::assemble_token(const span_type & token_text) const -> token_type @@ -483,9 +502,19 @@ namespace xo { ++ix; break; case ':': - tk_type = tokentype::tk_colon; - ++ix; + { + log && log("colon or assignment token"); + + if (*(ix + 1) == '=') { + tk_type = tokentype::tk_assign; + ++ix; + ++ix; + } else { + tk_type = tokentype::tk_colon; + ++ix; + } break; + } case '=': tk_type = tokentype::tk_singleassign; ++ix; @@ -575,9 +604,33 @@ namespace xo { */ const CharT * tk_start = ix; - if (is_punctuation(*ix)) { + if (is_1char_punctuation(*ix)) { /* 1-character token */ ++ix; + } else if (is_2char_punctuation(*ix)) { + CharT ch1 = *ix; + + ++ix; + + if (ix == input.hi()) { + /* need more input to know if/when token complete */ + this->prefix_ += std::string(tk_start, input.hi()); + + log && log(xtag("captured-prefix", this->prefix_)); + } else { + CharT ch2 = *ix; + + if (((ch2 >= '0') && (ch2 <= '9')) + || ((ch2 >= 'A') && (ch2 <= 'Z')) + || ((ch2 >= 'a') && (ch2 <= 'z'))) + { + /* treat as 1 char punctuation */ + ; + } else { + /* include next char */ + ++ix; + } + } } else if (*ix == '"') { bool complete_flag = false; @@ -618,8 +671,12 @@ namespace xo { * - punctuation */ for (; ix != input.hi(); ++ix) { - if (is_whitespace(*ix) || is_punctuation(*ix)) + if (is_whitespace(*ix) + || is_1char_punctuation(*ix) + || is_2char_punctuation(*ix)) + { break; + } } if (ix == input.hi()) { diff --git a/utest/tokenizer.test.cpp b/utest/tokenizer.test.cpp index b5d8303a..8a821b96 100644 --- a/utest/tokenizer.test.cpp +++ b/utest/tokenizer.test.cpp @@ -99,6 +99,9 @@ namespace xo { {"\"tab to the right [\\t], to the right [\\t]\"", false, token::string_token("tab to the right [\t], to the right [\t]"), true}, + {":", false, token::colon(), true}, + {":=", false, token::assign_token(), true}, + {"symbol", false, token::symbol_token("symbol"), true}, {"type", false, token::type(), true}, @@ -162,6 +165,78 @@ namespace xo { } } + namespace { + struct testcase2_tkz { + std::string input_; + bool expect_throw_; + std::vector expected_tk_v_; + }; + + std::vector + s_testcase2_v = { + {"def foo : f64 = 3.141;", + false, + {token::def(), + token::symbol_token("foo"), + token::colon(), + token::symbol_token("f64"), + token::singleassign(), + token::f64_token("3.141"), + token::semicolon() + }} + }; + } + + TEST_CASE("tokenizer2", "[tokenizer]") { + for (std::size_t i_tc = 0, n_tc = s_testcase2_v.size(); i_tc < n_tc; ++i_tc) { + const testcase2_tkz & testcase = s_testcase2_v[i_tc]; + + INFO(xtag("input", testcase.input_)); + INFO(xtag("i_tc", i_tc)); + + using tokenizer + = xo::scm::tokenizer; + + tokenizer tkz; + tokenizer::span_type + in_span(testcase.input_.c_str(), + testcase.input_.c_str() + testcase.input_.size()); + + for (int i_tk = 0, n_tk = testcase.expected_tk_v_.size(); + i_tk < n_tk; ++i_tk) + { + INFO(xtag("i_tk", i_tk)); + + auto res = tkz.scan2(in_span, in_span.empty()); + const auto & tk = res.first; + + if (tk.is_valid()) + REQUIRE(tk.tk_type() == testcase.expected_tk_v_[i_tk].tk_type()); + if (tk.tk_type() == tokentype::tk_i64) + { + REQUIRE(!tk.text().empty()); + REQUIRE(tk.i64_value() == testcase.expected_tk_v_[i_tk].i64_value()); + } else if (tk.tk_type() == tokentype::tk_f64) + { + REQUIRE(!tk.text().empty()); + REQUIRE(tk.f64_value() == testcase.expected_tk_v_[i_tk].f64_value()); + } else if(tk.tk_type() == tokentype::tk_string) + { + /* tk.text() can be empty, consider input "" */ + REQUIRE(tk.text() == testcase.expected_tk_v_[i_tk].text()); + } else if(tk.tk_type() == tokentype::tk_symbol) + { + REQUIRE(!tk.text().empty()); + REQUIRE(tk.text() == testcase.expected_tk_v_[i_tk].text()); + } else { + REQUIRE(tk.text().empty()); + } + + in_span = in_span.after_prefix(res.second); + } + } + } /*TEST_CASE(tokenizer2)*/ + } /*namespace ut*/ } /*namespace xo*/ From 75b0383e668597fdadf8bfc307d6538563f10a59 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 24 Aug 2024 12:30:34 -0400 Subject: [PATCH 1463/2524] xo-tokenizer: * token --- include/xo/tokenizer/token.hpp | 2 ++ include/xo/tokenizer/tokenizer.hpp | 10 ++++++++++ utest/tokenizer.test.cpp | 23 +++++++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/include/xo/tokenizer/token.hpp b/include/xo/tokenizer/token.hpp index 9944cb3d..988b4976 100644 --- a/include/xo/tokenizer/token.hpp +++ b/include/xo/tokenizer/token.hpp @@ -80,6 +80,8 @@ namespace xo { static token assign_token() { return token(tokentype::tk_assign); } static token yields() { return token(tokentype::tk_yields); } + static token star_token() { return token(tokentype::tk_star); } + static token type() { return token(tokentype::tk_type); } static token def() { return token(tokentype::tk_def); } static token lambda() { return token(tokentype::tk_lambda); } diff --git a/include/xo/tokenizer/tokenizer.hpp b/include/xo/tokenizer/tokenizer.hpp index bb67eb13..329f5226 100644 --- a/include/xo/tokenizer/tokenizer.hpp +++ b/include/xo/tokenizer/tokenizer.hpp @@ -154,6 +154,9 @@ namespace xo { case '+': /* can't be punctuation -- can appear inside f64 token: e.g. 1.23e+4 */ return false; + case '*': + /* not punctuation -- allowed in symbol */ + return false; case '.': /* can't be punctuation -- can appear inside f64 token: e.g. 1.23 */ return false; @@ -501,6 +504,13 @@ namespace xo { tk_type = tokentype::tk_semicolon; ++ix; break; + case '*': + /* '*' isn't punctuation, since can appear within symbol. + * However it cannot begin a symbol.. + */ + tk_type = tokentype::tk_star; + ++ix; + break; case ':': { log && log("colon or assignment token"); diff --git a/utest/tokenizer.test.cpp b/utest/tokenizer.test.cpp index 8a821b96..e0ec6a56 100644 --- a/utest/tokenizer.test.cpp +++ b/utest/tokenizer.test.cpp @@ -183,6 +183,29 @@ namespace xo { token::singleassign(), token::f64_token("3.141"), token::semicolon() + }}, + {"def foo = lambda (x : f64) { def y = x * x; y; }", + false, + {token::def(), + token::symbol_token("foo"), + token::singleassign(), + token::lambda(), + token::leftparen(), + token::symbol_token("x"), + token::colon(), + token::symbol_token("f64"), + token::rightparen(), + token::leftbrace(), + token::def(), + token::symbol_token("y"), + token::singleassign(), + token::symbol_token("x"), + token::star_token(), + token::symbol_token("x"), + token::semicolon(), + token::symbol_token("y"), + token::semicolon(), + token::rightbrace() }} }; } From ec1e45d2ed5a9cb604dea1a12819de1fb73ff5cb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 27 Aug 2024 16:18:22 -0400 Subject: [PATCH 1464/2524] xo-reader: bugfix: + missing exprstatetype.let1expr --- include/xo/reader/exprstate.hpp | 5 +++++ src/reader/exprstate.cpp | 2 ++ src/reader/let1_xs.cpp | 6 +++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index b22bf85f..5af05f86 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -38,6 +38,11 @@ namespace xo { **/ sequenceexpr, + /** handle let1 (single local variable) + * see @ref let1_xs + **/ + let1expr, + expect_rhs_expression, expect_symbol, expect_type, diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index dfb490ea..aa32b763 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -35,6 +35,8 @@ namespace xo { return "parenexpr"; case exprstatetype::sequenceexpr: return "sequenceexpr"; + case exprstatetype::let1expr: + return "let1expr"; case exprstatetype::expect_rhs_expression: return "expect_rhs_expression"; case exprstatetype::expect_symbol: diff --git a/src/reader/let1_xs.cpp b/src/reader/let1_xs.cpp index f248d1dd..e9c17651 100644 --- a/src/reader/let1_xs.cpp +++ b/src/reader/let1_xs.cpp @@ -49,7 +49,8 @@ namespace xo { let1_xs::let1_xs(std::string lhs_name, rp rhs) - : lhs_name_{std::move(lhs_name)}, + : exprstate(), + lhs_name_{std::move(lhs_name)}, rhs_{std::move(rhs)} {} @@ -57,6 +58,9 @@ namespace xo { let1_xs::on_expr(ref::brw expr, parserstatemachine * p_psm) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + ref::brw def_expr = DefineExpr::from(expr); if (def_expr) { From 76f292d40db0d5ad341d93da797cc51265dfd463 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 27 Aug 2024 16:18:53 -0400 Subject: [PATCH 1465/2524] xo-reader: bugfix: call on_{left,right}_brace_token() --- src/reader/exprstate.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index aa32b763..64aa65b2 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -308,8 +308,16 @@ namespace xo { case tokentype::tk_leftbracket: case tokentype::tk_rightbracket: + assert(false); + break; + case tokentype::tk_leftbrace: + this->on_leftbrace_token(tk, p_psm); + return; + case tokentype::tk_rightbrace: + this->on_rightbrace_token(tk, p_psm); + return; case tokentype::tk_leftangle: case tokentype::tk_rightangle: From d88d2713db0260599d37b414244ad8ad419dba77 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 27 Aug 2024 16:20:44 -0400 Subject: [PATCH 1466/2524] xo-reader: doc: ++ comments --- include/xo/reader/parserstatemachine.hpp | 3 +++ include/xo/reader/progress_xs.hpp | 9 ++++++++- src/reader/expect_expr_xs.cpp | 2 -- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/include/xo/reader/parserstatemachine.hpp b/include/xo/reader/parserstatemachine.hpp index 3ad71b13..e23cd262 100644 --- a/include/xo/reader/parserstatemachine.hpp +++ b/include/xo/reader/parserstatemachine.hpp @@ -50,6 +50,9 @@ namespace xo { exprstatestack * p_stack_; /** stack of environment frames, one for each enclosing lambda **/ envframestack * p_env_stack_; + /** if non-null, store next non-nested complete expressions in + * *p_emit_expr + **/ rp * p_emit_expr_; }; } /*namespace scm*/ diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index b370a1f9..1c985ed3 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -11,7 +11,11 @@ namespace xo { namespace scm { - /* represent an infix operator */ + /** represent an infix operator. + * + * See @ref progress_xs::assemble_expr() for translation + * to Expression + **/ enum class optype { invalid = -1, @@ -26,6 +30,9 @@ namespace xo { extern const char * optype_descr(optype x); + /** report operator precedence. + * lowest operator precedence is 1 + **/ extern int precedence(optype x); diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index c07c5d42..b4383f0b 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -92,8 +92,6 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - //constexpr const char * self_name = "exprstate::on_leftparen"; - /* push lparen_0 to remember to look for subsequent rightparen. */ sequence_xs::start(p_psm); } From 00625ee063d388aae7bfe5662539cf1bf71a4559 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 27 Aug 2024 16:21:54 -0400 Subject: [PATCH 1467/2524] xo-reader: debug: + log output --- src/reader/parserstatemachine.cpp | 8 ++++++++ src/reader/sequence_xs.cpp | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/src/reader/parserstatemachine.cpp b/src/reader/parserstatemachine.cpp index 9a7ce55d..2e2abd2c 100644 --- a/src/reader/parserstatemachine.cpp +++ b/src/reader/parserstatemachine.cpp @@ -32,11 +32,19 @@ namespace xo { void parserstatemachine::push_envframe(envframe x) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("frame", x)); + p_env_stack_->push_envframe(std::move(x)); } void parserstatemachine::pop_envframe() { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + p_env_stack_->pop_envframe(); } } /*namespace scm*/ diff --git a/src/reader/sequence_xs.cpp b/src/reader/sequence_xs.cpp index c9ee71f3..97aa0955 100644 --- a/src/reader/sequence_xs.cpp +++ b/src/reader/sequence_xs.cpp @@ -35,6 +35,11 @@ namespace xo { sequence_xs::on_expr(ref::brw expr, parserstatemachine * p_psm) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("expr", expr.promote())); + /* TODO: if expr is a DefineExpr, * then need to rewrite... * From 1145830bb1a3a1abf3254a8ccb4d13d31b781e7f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 27 Aug 2024 16:23:04 -0400 Subject: [PATCH 1468/2524] xo-parser: feat: + assign operator handling [wip, untested] --- include/xo/reader/progress_xs.hpp | 3 +++ src/reader/progress_xs.cpp | 28 ++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/include/xo/reader/progress_xs.hpp b/include/xo/reader/progress_xs.hpp index 1c985ed3..6e9db243 100644 --- a/include/xo/reader/progress_xs.hpp +++ b/include/xo/reader/progress_xs.hpp @@ -19,8 +19,11 @@ namespace xo { enum class optype { invalid = -1, + op_assign, + op_add, op_subtract, + op_multiply, op_divide, diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index ed873e35..fcb32142 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -4,10 +4,13 @@ #include "exprstatestack.hpp" #include "expect_expr_xs.hpp" #include "parserstatemachine.hpp" +#include "xo/expression/AssignExpr.hpp" #include "xo/expression/Apply.hpp" namespace xo { using xo::ast::Expression; + using xo::ast::AssignExpr; + using xo::ast::Variable; using xo::ast::Apply; namespace scm { @@ -16,6 +19,8 @@ namespace xo { switch (x) { case optype::invalid: return "?optype"; + case optype::op_assign: + return "op:="; case optype::op_add: return "op+"; case optype::op_subtract: @@ -37,13 +42,16 @@ namespace xo { case optype::n_optype: return 0; + case optype::op_assign: + return 1; + case optype::op_add: case optype::op_subtract: - return 1; + return 2; case optype::op_multiply: case optype::op_divide: - return 2; + return 3; } return 0; @@ -108,6 +116,22 @@ namespace xo { case optype::invalid: return this->lhs_; + case optype::op_assign: + { + ref::brw lhs = Variable::from(this->lhs_); + + if (!lhs) { + throw std::runtime_error + (tostr("progress_xs::assemble_expr", + " expect variable on lhs of assignment operator :=", + xtag("lhs", lhs_), + xtag("rhs", rhs_))); + } + + return AssignExpr::make(lhs.promote(), + this->rhs_); + } + case optype::op_add: return Apply::make_add2_f64(this->lhs_, this->rhs_); From 22d4c6c601065d9a5ccfc2df1ac6d3ff076e2e22 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 27 Aug 2024 16:23:33 -0400 Subject: [PATCH 1469/2524] xo-reader: + block utest + assignment utest [wip, not passsing] --- src/reader/expect_expr_xs.cpp | 2 +- utest/reader.test.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index b4383f0b..f8a17836 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -188,7 +188,7 @@ namespace xo { scope log(XO_DEBUG(c_debug_flag)); log && log(xtag("exstype", this->exs_type_), - xtag("expr", expr)); + xtag("expr", expr.promote())); std::unique_ptr self = p_psm->pop_exprstate(); diff --git a/utest/reader.test.cpp b/utest/reader.test.cpp index 2c9a490d..d73fc972 100644 --- a/utest/reader.test.cpp +++ b/utest/reader.test.cpp @@ -18,7 +18,8 @@ namespace xo { //{"def foo : f64 = 2.0 * 3.14159265;"}, {"def foo = lambda (x : f64) 3.1415965;"}, {"def foo = lambda (x : f64, y : f64) 3.1415965;"}, - {"def foo = lambda (x : f64) x;"} + {"def foo = lambda (x : f64) x;"}, + {"def foo = lambda (x : f64) { def y = x * x; y; }"}, }; } From 4179196e219521c766a087cc29dfe9b55bb9ec54 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 27 Aug 2024 16:30:08 -0400 Subject: [PATCH 1470/2524] xo-reflect: nit: README typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e43dfd8..d39421b6 100644 --- a/README.md +++ b/README.md @@ -51,5 +51,5 @@ $ cmake --build xo-reflect/.build-ccov ### LSP support ``` $ cd xo-reflect -$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +$ ln -s .build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree ``` From ac4ee7d6b85abab41c8efd5e9d33c465d39810a0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 28 Aug 2024 00:38:05 -0400 Subject: [PATCH 1471/2524] xo-reader: misc bugfixes + logging [wip, utests not passing] --- include/xo/reader/define_xs.hpp | 5 +++ include/xo/reader/envframestack.hpp | 5 ++- include/xo/reader/exprstate.hpp | 9 ++++ include/xo/reader/exprstatestack.hpp | 1 + include/xo/reader/paren_xs.hpp | 9 ++++ include/xo/reader/parserstatemachine.hpp | 19 ++++++++ src/reader/define_xs.cpp | 57 ++++++++++++++++++++++-- src/reader/expect_expr_xs.cpp | 2 +- src/reader/expect_symbol_xs.cpp | 4 +- src/reader/exprstate.cpp | 2 + src/reader/exprstatestack.cpp | 4 +- src/reader/let1_xs.cpp | 5 +-- src/reader/paren_xs.cpp | 22 +++++++-- src/reader/parserstatemachine.cpp | 48 ++++++++++++++++++++ 14 files changed, 175 insertions(+), 17 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index ebb7b161..13b3d558 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -56,6 +56,11 @@ namespace xo { n_defexprstatetype, }; + extern const char * defexprstatetype_descr(defexprstatetype x); + + std::ostream & + operator<<(std::ostream & os, defexprstatetype x); + /** @class define_xs * @brief state to provide parsing of a define-expression **/ diff --git a/include/xo/reader/envframestack.hpp b/include/xo/reader/envframestack.hpp index ac933dae..e438f47f 100644 --- a/include/xo/reader/envframestack.hpp +++ b/include/xo/reader/envframestack.hpp @@ -65,7 +65,10 @@ namespace xo { inline std::ostream & operator<< (std::ostream & os, const envframestack * x) { - x->print(os); + if (x) + x->print(os); + else + os << "nullptr"; return os; } } /*namespace scm*/ diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 5af05f86..8c5c9d6a 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -189,6 +189,15 @@ namespace xo { x.print(os); return os; } + + inline std::ostream & + operator<< (std::ostream & os, const exprstate * x) { + if (x) + x->print(os); + else + os << "nullptr"; + return os; + }; } /*namespace scm*/ } /*namespace xo*/ diff --git a/include/xo/reader/exprstatestack.hpp b/include/xo/reader/exprstatestack.hpp index c5ee0894..36fe15b4 100644 --- a/include/xo/reader/exprstatestack.hpp +++ b/include/xo/reader/exprstatestack.hpp @@ -6,6 +6,7 @@ #pragma once #include "exprstate.hpp" +#include "xo/indentlog/print/vector.hpp" namespace xo { namespace scm { diff --git a/include/xo/reader/paren_xs.hpp b/include/xo/reader/paren_xs.hpp index 429a2a6e..514074d1 100644 --- a/include/xo/reader/paren_xs.hpp +++ b/include/xo/reader/paren_xs.hpp @@ -19,6 +19,15 @@ namespace xo { n_parenexprstatetype, }; + extern const char * + parenexprstatetype_descr(parenexprstatetype x); + + inline std::ostream & + operator<< (std::ostream & os, parenexprstatetype x) { + os << parenexprstatetype_descr(x); + return os; + } + /** @class paren_xs * @brief state machine for handling parentheses in expressions **/ diff --git a/include/xo/reader/parserstatemachine.hpp b/include/xo/reader/parserstatemachine.hpp index e23cd262..ac3c5cd8 100644 --- a/include/xo/reader/parserstatemachine.hpp +++ b/include/xo/reader/parserstatemachine.hpp @@ -21,6 +21,7 @@ namespace xo { public: using Expression = xo::ast::Expression; using Variable = xo::ast::Variable; + using token_type = token; public: parserstatemachine(exprstatestack * p_stack, @@ -42,6 +43,18 @@ namespace xo { void push_envframe(envframe x); void pop_envframe(); + // ----- parsing outputs ----- + + void on_symbol(const std::string & symbol); + + // ---- parsing inputs ----- + + void on_leftbrace_token(const token_type & tk); + void on_rightbrace_token(const token_type & tk); + + /** write human-readable representation on @p os **/ + void print(std::ostream & os) const; + public: /** stack of incomplete parser work. * generally speaking, push when to start new work for nested content; @@ -55,6 +68,12 @@ namespace xo { **/ rp * p_emit_expr_; }; + + inline std::ostream & + operator<<(std::ostream & os, const parserstatemachine & x) { + x.print(os); + return os; + } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 04c37c5b..88ddb9d3 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -9,6 +9,33 @@ namespace xo { namespace scm { + // ----- defexprstatetype ----- + + const char * + defexprstatetype_descr(defexprstatetype x) { + switch(x) { + case defexprstatetype::invalid: return "invalid"; + case defexprstatetype::def_0: return "def_0"; + case defexprstatetype::def_1: return "def_1"; + case defexprstatetype::def_2: return "def_2"; + case defexprstatetype::def_3: return "def_3"; + case defexprstatetype::def_4: return "def_4"; + case defexprstatetype::def_5: return "def_5"; + case defexprstatetype::def_6: return "def_6"; + case defexprstatetype::n_defexprstatetype: break; + } + + return "???defexprstatetype"; + } + + std::ostream & + operator<<(std::ostream & os, defexprstatetype x) { + os << defexprstatetype_descr(x); + return os; + } + + // ----- define_xs ----- + std::unique_ptr define_xs::make() { return std::make_unique(define_xs(DefineExprAccess::make_empty())); @@ -17,6 +44,9 @@ namespace xo { void define_xs::start(parserstatemachine * p_psm) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + p_psm->push_exprstate(define_xs::make()); p_psm->top_exprstate().on_def_token(token_type::def(), p_psm); } @@ -31,6 +61,11 @@ namespace xo { define_xs::on_expr(ref::brw expr, parserstatemachine * p_psm) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log("defxs_type", defxs_type_); + if (this->defxs_type_ == defexprstatetype::def_5) { /* have all the ingredients to create an expression * representing a definition @@ -59,6 +94,11 @@ namespace xo { define_xs::on_symbol(const std::string & symbol_name, parserstatemachine * p_psm) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log("defxs_type", defxs_type_); + if (this->defxs_type_ == defexprstatetype::def_1) { this->defxs_type_ = defexprstatetype::def_2; this->def_expr_->assign_lhs_name(symbol_name); @@ -72,6 +112,11 @@ namespace xo { define_xs::on_typedescr(TypeDescr td, parserstatemachine * p_psm) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log("defxs_type", defxs_type_); + if (this->defxs_type_ == defexprstatetype::def_3) { this->defxs_type_ = defexprstatetype::def_4; this->cvt_expr_ = ConvertExprAccess::make(td /*dest_type*/, @@ -92,7 +137,7 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - //constexpr const char * self_name = "define_xs::on_def_token"; + log && log("defxs_type", defxs_type_); if (this->defxs_type_ == defexprstatetype::def_0) { this->defxs_type_ = defexprstatetype::def_1; @@ -110,7 +155,7 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - //constexpr const char * self_name = "define_xs::on_colon_token"; + log && log("defxs_type", defxs_type_); if (this->defxs_type_ == defexprstatetype::def_2) { this->defxs_type_ = defexprstatetype::def_3; @@ -128,7 +173,7 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - //constexpr const char * self_name = "exprstate::on_semicolon"; + log && log("defxs_type", defxs_type_); if (this->defxs_type_ == defexprstatetype::def_6) { rp expr = this->def_expr_; @@ -150,6 +195,8 @@ namespace xo { constexpr const char * self_name = "exprstate::on_singleassign"; + log && log("defxs_type", defxs_type_); + /* * def foo = 1 ; * def foo : f64 = 1 ; @@ -203,7 +250,9 @@ namespace xo { void define_xs::print(std::ostream & os) const { os << "top_exprstate().on_rightbrace_token(tk, p_psm); + p_psm->on_rightbrace_token(tk); } else { exprstate::on_rightbrace_token(tk, p_psm); } diff --git a/src/reader/expect_symbol_xs.cpp b/src/reader/expect_symbol_xs.cpp index 930d12b0..27a94b64 100644 --- a/src/reader/expect_symbol_xs.cpp +++ b/src/reader/expect_symbol_xs.cpp @@ -33,9 +33,7 @@ namespace xo { */ std::unique_ptr self = p_psm->pop_exprstate(); - - p_psm->top_exprstate().on_symbol(tk.text(), p_psm); - return; + p_psm->on_symbol(tk.text()); } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 64aa65b2..f472b070 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -271,6 +271,7 @@ namespace xo { scope log(XO_DEBUG(c_debug_flag)); log && log(xtag("tk", tk)); log && log(xtag("state", *this)); + log && log(xtag("psm", *p_psm)); switch (tk.tk_type()) { @@ -405,6 +406,7 @@ namespace xo { void exprstate::print(std::ostream & os) const { os << ""; } diff --git a/src/reader/exprstatestack.cpp b/src/reader/exprstatestack.cpp index 07ca85bb..2e070c48 100644 --- a/src/reader/exprstatestack.cpp +++ b/src/reader/exprstatestack.cpp @@ -23,7 +23,7 @@ namespace xo { exprstatestack::push_exprstate(std::unique_ptr exs) { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag), - xtag("exs", *exs)); + xtag("exs", exs.get())); std::size_t z = stack_.size(); @@ -59,7 +59,7 @@ namespace xo { for (std::size_t i = 0, z = stack_.size(); i < z; ++i) { os << " [" << z-i-1 << "] " - << stack_[i] + << stack_[i].get() << std::endl; } diff --git a/src/reader/let1_xs.cpp b/src/reader/let1_xs.cpp index e9c17651..0afbd415 100644 --- a/src/reader/let1_xs.cpp +++ b/src/reader/let1_xs.cpp @@ -39,8 +39,7 @@ namespace xo { const rp & rhs, parserstatemachine * p_psm) { - p_psm->push_exprstate(let1_xs::make(std::move(lhs_name), - std::move(rhs))); + p_psm->push_exprstate(let1_xs::make(lhs_name, rhs)); expect_expr_xs::start(true /*allow_defs*/, true /*cxl_on_rightbrace*/, @@ -111,7 +110,7 @@ namespace xo { * -- remember we pushed let1_xs to handle an embedded def-expr * in a sequence */ - p_psm->top_exprstate().on_rightbrace_token(tk, p_psm); + p_psm->on_rightbrace_token(tk); } } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 5b1974cc..25af9ee2 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -8,8 +8,22 @@ namespace xo { namespace scm { + const char * + parenexprstatetype_descr(parenexprstatetype x) + { + switch(x) { + case parenexprstatetype::invalid: return "invalid"; + case parenexprstatetype::lparen_0: return "lparen_0"; + case parenexprstatetype::lparen_1: return "lparen_1"; + case parenexprstatetype::n_parenexprstatetype: break; + } + + return "???parenexprstatetype"; + } + paren_xs::paren_xs() - : parenxs_type_{parenexprstatetype::lparen_0} + : exprstate(exprstatetype::parenexpr), + parenxs_type_{parenexprstatetype::lparen_0} {} std::unique_ptr @@ -221,13 +235,15 @@ namespace xo { void paren_xs::print(std::ostream & os) const { os << ""; } - } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/parserstatemachine.cpp b/src/reader/parserstatemachine.cpp index 2e2abd2c..9ed21d0a 100644 --- a/src/reader/parserstatemachine.cpp +++ b/src/reader/parserstatemachine.cpp @@ -47,6 +47,54 @@ namespace xo { p_env_stack_->pop_envframe(); } + + void + parserstatemachine::on_symbol(const std::string & x) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("x", x), + xtag("psm", *this)); + + this->p_stack_ + ->top_exprstate().on_symbol(x, this); + } + + void + parserstatemachine::on_leftbrace_token(const token_type & tk) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("tk", tk), + xtag("psm", *this)); + + this->p_stack_ + ->top_exprstate().on_leftbrace_token(tk, this); + } + + void + parserstatemachine::on_rightbrace_token(const token_type & tk) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("tk", tk), + xtag("psm", *this)); + + this->p_stack_ + ->top_exprstate().on_rightbrace_token(tk, this); + } + + void + parserstatemachine::print(std::ostream & os) const { + os << ""; + } } /*namespace scm*/ } /*namespace xo*/ From 24d9d504b513f23cdc25bd8fe3354786c7b375c8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 28 Aug 2024 00:57:17 -0400 Subject: [PATCH 1472/2524] xo-expression: minor print logging fixes --- include/xo/expression/Constant.hpp | 11 +++++++---- src/expression/Variable.cpp | 9 ++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/include/xo/expression/Constant.hpp b/include/xo/expression/Constant.hpp index 77098a5b..b2c9f71d 100644 --- a/include/xo/expression/Constant.hpp +++ b/include/xo/expression/Constant.hpp @@ -66,10 +66,13 @@ namespace xo { } virtual void display(std::ostream & os) const override { - os << "short_name()) - << xtag("value", value_) - << ">"; + os << "short_name()); + else + os << xtag("type", "nullptr");; + os << xtag("value", value_); + os << ">"; } private: diff --git a/src/expression/Variable.cpp b/src/expression/Variable.cpp index 766e0c67..18434afb 100644 --- a/src/expression/Variable.cpp +++ b/src/expression/Variable.cpp @@ -18,9 +18,12 @@ namespace xo { void Variable::display(std::ostream & os) const { os << "valuetype()->short_name()) - << ">"; + << xtag("name", name_); + if (this->valuetype()) + os << xtag("type", this->valuetype()->short_name()); + else + os << xtag("type", "nullptr"); + os << ">"; } /*display*/ } /*namespace ast*/ } /*namespace xo*/ From bc30b34bc23cd5129096d89ea59d3c20062d6c84 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 28 Aug 2024 09:33:58 -0400 Subject: [PATCH 1473/2524] xo-reader: + on_expr_with_semicolon() [wip, utest not passing] --- include/xo/reader/define_xs.hpp | 2 ++ include/xo/reader/expect_expr_xs.hpp | 6 ++++++ include/xo/reader/exprstate.hpp | 4 ++++ include/xo/reader/lambda_xs.hpp | 2 ++ include/xo/reader/parserstatemachine.hpp | 3 +++ src/reader/define_xs.cpp | 7 +++++++ src/reader/expect_expr_xs.cpp | 15 ++++++++++++++ src/reader/exprstate.cpp | 13 ++++++++++++ src/reader/lambda_xs.cpp | 8 ++++++++ src/reader/parserstatemachine.cpp | 26 ++++++++++++++++++++++++ src/reader/progress_xs.cpp | 4 ++-- 11 files changed, 88 insertions(+), 2 deletions(-) diff --git a/include/xo/reader/define_xs.hpp b/include/xo/reader/define_xs.hpp index 13b3d558..880dd309 100644 --- a/include/xo/reader/define_xs.hpp +++ b/include/xo/reader/define_xs.hpp @@ -81,6 +81,8 @@ namespace xo { virtual void on_expr(ref::brw expr, parserstatemachine * p_psm) override; + virtual void on_expr_with_semicolon(ref::brw expr, + parserstatemachine * p_psm) override; virtual void on_symbol(const std::string & symbol_name, parserstatemachine * p_psm) override; virtual void on_typedescr(TypeDescr td, diff --git a/include/xo/reader/expect_expr_xs.hpp b/include/xo/reader/expect_expr_xs.hpp index c3e8a513..646a803a 100644 --- a/include/xo/reader/expect_expr_xs.hpp +++ b/include/xo/reader/expect_expr_xs.hpp @@ -47,6 +47,12 @@ namespace xo { /** update exprstate in response to a successfully-parsed subexpression **/ virtual void on_expr(ref::brw expr, parserstatemachine * p_psm) override; + /** update exprstate in response to a successfully-parsed subexpression, + * that's terminated by semicolon ';' + **/ + virtual void on_expr_with_semicolon(ref::brw expr, + parserstatemachine * p_psm) override; + private: static std::unique_ptr make(bool allow_defs, diff --git a/include/xo/reader/exprstate.hpp b/include/xo/reader/exprstate.hpp index 8c5c9d6a..a6acad1f 100644 --- a/include/xo/reader/exprstate.hpp +++ b/include/xo/reader/exprstate.hpp @@ -107,6 +107,10 @@ namespace xo { virtual void on_expr(ref::brw expr, parserstatemachine * p_psm); + /** update exprstate in response to a successfully-parsed subexpression, that ends with semicolon **/ + virtual void on_expr_with_semicolon(ref::brw expr, + parserstatemachine * p_psm); + /** update exprstate when expecting a symbol **/ virtual void on_symbol(const std::string & symbol, parserstatemachine * p_psm); diff --git a/include/xo/reader/lambda_xs.hpp b/include/xo/reader/lambda_xs.hpp index 79c084ab..830135a3 100644 --- a/include/xo/reader/lambda_xs.hpp +++ b/include/xo/reader/lambda_xs.hpp @@ -48,6 +48,8 @@ namespace xo { parserstatemachine * p_psm) override; virtual void on_expr(ref::brw expr, parserstatemachine * p_psm) override; + virtual void on_expr_with_semicolon(ref::brw expr, + parserstatemachine * p_psm) override; virtual void on_semicolon_token(const token_type & tk, parserstatemachine * p_psm) override; diff --git a/include/xo/reader/parserstatemachine.hpp b/include/xo/reader/parserstatemachine.hpp index ac3c5cd8..065513c5 100644 --- a/include/xo/reader/parserstatemachine.hpp +++ b/include/xo/reader/parserstatemachine.hpp @@ -45,10 +45,13 @@ namespace xo { // ----- parsing outputs ----- + void on_expr(ref::brw expr); + void on_expr_with_semicolon(ref::brw expr); void on_symbol(const std::string & symbol); // ---- parsing inputs ----- + void on_semicolon_token(const token_type & tk); void on_leftbrace_token(const token_type & tk); void on_rightbrace_token(const token_type & tk); diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index 88ddb9d3..d251652a 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -88,6 +88,13 @@ namespace xo { } exprstate::on_expr(expr, p_psm); + void + define_xs::on_expr_with_semicolon(ref::brw expr, + parserstatemachine * p_psm) + { + this->on_expr(expr, p_psm); + /* semicolon is allowed to terminate def expr */ + this->on_semicolon_token(token_type::semicolon(), p_psm); } void diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index 3fe2aacf..af27d720 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -195,6 +195,21 @@ namespace xo { p_psm->top_exprstate().on_expr(expr, p_psm); } /*on_expr*/ + void + expect_expr_xs::on_expr_with_semicolon(ref::brw expr, + parserstatemachine * p_psm) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("exstype", this->exs_type_), + xtag("expr", expr.promote())); + + std::unique_ptr self = p_psm->pop_exprstate(); + + p_psm->on_expr_with_semicolon(expr); + } /*on_expr_with_semicolon*/ + } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index f472b070..550e20d0 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -387,6 +387,19 @@ namespace xo { assert(false); } /*on_expr*/ + void + exprstate::on_expr_with_semicolon(ref::brw expr, + parserstatemachine * /*p_psm*/) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("exstype", this->exs_type_), + xtag("expr", expr)); + + assert(false); + } /*on_expr_with_semicolon*/ + void exprstate::on_symbol(const std::string & symbol_name, parserstatemachine * /*p_psm*/) diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index 00fb5c0e..8aed4511 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -66,6 +66,14 @@ namespace xo { } } + void + lambda_xs::on_expr_with_semicolon(ref::brw expr, + parserstatemachine * p_psm) + { + this->on_expr(expr, p_psm); + this->on_semicolon_token(token_type::semicolon(), p_psm); + } + void lambda_xs::on_semicolon_token(const token_type & tk, parserstatemachine * p_psm) diff --git a/src/reader/parserstatemachine.cpp b/src/reader/parserstatemachine.cpp index 9ed21d0a..44700814 100644 --- a/src/reader/parserstatemachine.cpp +++ b/src/reader/parserstatemachine.cpp @@ -48,6 +48,32 @@ namespace xo { p_env_stack_->pop_envframe(); } + void + parserstatemachine::on_expr(ref::brw x) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("x", x), + xtag("psm", *this)); + + this->p_stack_ + ->top_exprstate().on_expr(x, this); + } + + void + parserstatemachine::on_expr_with_semicolon(ref::brw x) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("x", x), + xtag("psm", *this)); + + this->p_stack_ + ->top_exprstate().on_expr_with_semicolon(x, this); + } + void parserstatemachine::on_symbol(const std::string & x) { diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index fcb32142..d716bd0c 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -215,7 +215,7 @@ namespace xo { } void - progress_xs::on_semicolon_token(const token_type & tk, + progress_xs::on_semicolon_token(const token_type & /*tk*/, parserstatemachine * p_psm) { /* note: implementation parllels .on_rightparen_token() */ @@ -227,7 +227,7 @@ namespace xo { std::unique_ptr self = p_psm->pop_exprstate(); - p_psm->top_exprstate().on_expr(expr, p_psm); + p_psm->on_expr_with_semicolon(expr); /* control here on input like: * (1.234; From 84e6d3f347b23c0f109a06c74b6503d6d75312bf Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 28 Aug 2024 09:34:30 -0400 Subject: [PATCH 1474/2524] xo-reader: ++ logging --- include/xo/reader/exprstatestack.hpp | 5 ++++- include/xo/reader/lambda_xs.hpp | 11 +++++++++++ src/reader/define_xs.cpp | 24 ++++++++++++++---------- src/reader/expect_expr_xs.cpp | 5 +++-- src/reader/lambda_xs.cpp | 24 ++++++++++++++++++++++++ src/reader/paren_xs.cpp | 1 + src/reader/parserstatemachine.cpp | 13 +++++++++++++ src/reader/progress_xs.cpp | 10 ++++------ 8 files changed, 74 insertions(+), 19 deletions(-) diff --git a/include/xo/reader/exprstatestack.hpp b/include/xo/reader/exprstatestack.hpp index 36fe15b4..b4e57449 100644 --- a/include/xo/reader/exprstatestack.hpp +++ b/include/xo/reader/exprstatestack.hpp @@ -57,7 +57,10 @@ namespace xo { inline std::ostream & operator<< (std::ostream & os, const exprstatestack * x) { - x->print(os); + if (x) + x->print(os); + else + os << "nullptr"; return os; } } /*namespace scm*/ diff --git a/include/xo/reader/lambda_xs.hpp b/include/xo/reader/lambda_xs.hpp index 830135a3..728bc9e3 100644 --- a/include/xo/reader/lambda_xs.hpp +++ b/include/xo/reader/lambda_xs.hpp @@ -32,6 +32,15 @@ namespace xo { n_lambdastatetype }; + extern const char * + lambdastatetype_descr(lambdastatetype x); + + inline std::ostream & + operator<< (std::ostream & os, lambdastatetype x) { + os << lambdastatetype_descr(x); + return os; + } + /** @class lambda_xs * @brief parsing state-machine for a lambda-expression * @@ -53,6 +62,8 @@ namespace xo { virtual void on_semicolon_token(const token_type & tk, parserstatemachine * p_psm) override; + virtual void print(std::ostream & os) const override; + private: static std::unique_ptr make(); diff --git a/src/reader/define_xs.cpp b/src/reader/define_xs.cpp index d251652a..19f6d83d 100644 --- a/src/reader/define_xs.cpp +++ b/src/reader/define_xs.cpp @@ -64,7 +64,7 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - log && log("defxs_type", defxs_type_); + log && log(xtag("defxs_type", defxs_type_)); if (this->defxs_type_ == defexprstatetype::def_5) { /* have all the ingredients to create an expression @@ -84,10 +84,11 @@ namespace xo { rp def_expr = this->def_expr_; this->defxs_type_ = defexprstatetype::def_6; - return; + } else { + exprstate::on_expr(expr, p_psm); } + } - exprstate::on_expr(expr, p_psm); void define_xs::on_expr_with_semicolon(ref::brw expr, parserstatemachine * p_psm) @@ -177,6 +178,8 @@ namespace xo { define_xs::on_semicolon_token(const token_type & tk, parserstatemachine * p_psm) { + /* def expr consumes semicolon */ + constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); @@ -200,7 +203,7 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - constexpr const char * self_name = "exprstate::on_singleassign"; + constexpr const char * self_name = "define_xs::on_singleassign_token"; log && log("defxs_type", defxs_type_); @@ -237,7 +240,7 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - constexpr const char * self_name = "exprstate::on_rightparen"; + constexpr const char * self_name = "define_xs::on_rightparen"; this->illegal_input_error(self_name, tk); } @@ -249,7 +252,7 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); - constexpr const char * self_name = "exprstate::on_f64"; + constexpr const char * self_name = "define_xs::on_f64"; this->illegal_input_error(self_name, tk); } @@ -257,13 +260,14 @@ namespace xo { void define_xs::print(std::ostream & os) const { os << ""; } } /*namespace scm*/ diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index af27d720..3d238ede 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -31,7 +31,8 @@ namespace xo { void expect_expr_xs::start(bool allow_defs, bool cxl_on_rightbrace, - parserstatemachine * p_psm) { + parserstatemachine * p_psm) + { p_psm->push_exprstate(expect_expr_xs::make(allow_defs, cxl_on_rightbrace)); } @@ -192,7 +193,7 @@ namespace xo { std::unique_ptr self = p_psm->pop_exprstate(); - p_psm->top_exprstate().on_expr(expr, p_psm); + p_psm->on_expr(expr); } /*on_expr*/ void diff --git a/src/reader/lambda_xs.cpp b/src/reader/lambda_xs.cpp index 8aed4511..ab9b73d8 100644 --- a/src/reader/lambda_xs.cpp +++ b/src/reader/lambda_xs.cpp @@ -11,6 +11,22 @@ namespace xo { using xo::ast::Lambda; namespace scm { + const char * + lambdastatetype_descr(lambdastatetype x) { + switch(x) { + case lambdastatetype::invalid: return "invalid"; + case lambdastatetype::lm_0: return "lm_0"; + case lambdastatetype::lm_1: return "lm_1"; + case lambdastatetype::lm_2: return "lm_2"; + case lambdastatetype::lm_3: return "lm_3"; + default: break; + } + + return "???lambdastatetype"; + } + + // ----- lambda_xs - ---- + std::unique_ptr lambda_xs::make() { return std::make_unique(lambda_xs()); @@ -97,6 +113,14 @@ namespace xo { exprstate::on_semicolon_token(tk, p_psm); } + + void + lambda_xs::print(std::ostream & os) const { + os << ""; + } + } /*namespace scm*/ } /*namespace xo*/ diff --git a/src/reader/paren_xs.cpp b/src/reader/paren_xs.cpp index 25af9ee2..2c4ca484 100644 --- a/src/reader/paren_xs.cpp +++ b/src/reader/paren_xs.cpp @@ -235,6 +235,7 @@ namespace xo { void paren_xs::print(std::ostream & os) const { os << "top_exprstate().on_symbol(x, this); } + void + parserstatemachine::on_semicolon_token(const token_type & tk) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("tk", tk), + xtag("psm", *this)); + + this->p_stack_ + ->top_exprstate().on_semicolon_token(tk, this); + } + void parserstatemachine::on_leftbrace_token(const token_type & tk) { diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index d716bd0c..47bf2c04 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -237,14 +237,11 @@ namespace xo { * b. 1.234 pushes (in case operators) [lparen_0:expect_rhs_expression:expr_progress] * (see exprstate::on_f64()) * c. semicolon completes expr_progress [lparen_0:expect_rhs_expression] - * deliver expresssion to expect_rhs_expression.on_expr() - * (see exprstate::on_expr()) + * deliver expresssion to expect_rhs_expression.on_expr_with_semicolon() + * (see exprstate::on_expr_with_semicolon()) * d. expr_rhs_expression forwards expression to [lparen_0] - * e. lparen_0 advances to [lparen_1] - * f. now deliver semicolon; [lparen_1] rejects + * e. lparen_0 would advance to [lparen_1], but rejects semicolon */ - - p_psm->top_exprstate().on_semicolon_token(tk, p_psm); } void @@ -420,6 +417,7 @@ namespace xo { void progress_xs::print(std::ostream & os) const { os << " Date: Wed, 28 Aug 2024 12:58:49 -0400 Subject: [PATCH 1475/2524] xo-reader: ++ logging --- include/xo/reader/parserstatemachine.hpp | 1 + src/reader/expect_expr_xs.cpp | 16 +++++++++++++--- src/reader/expect_symbol_xs.cpp | 5 +++++ src/reader/exprstate.cpp | 8 ++++++++ src/reader/parserstatemachine.cpp | 13 +++++++++++++ src/reader/progress_xs.cpp | 3 +++ 6 files changed, 43 insertions(+), 3 deletions(-) diff --git a/include/xo/reader/parserstatemachine.hpp b/include/xo/reader/parserstatemachine.hpp index 065513c5..0e69d44c 100644 --- a/include/xo/reader/parserstatemachine.hpp +++ b/include/xo/reader/parserstatemachine.hpp @@ -52,6 +52,7 @@ namespace xo { // ---- parsing inputs ----- void on_semicolon_token(const token_type & tk); + void on_operator_token(const token_type & tk); void on_leftbrace_token(const token_type & tk); void on_rightbrace_token(const token_type & tk); diff --git a/src/reader/expect_expr_xs.cpp b/src/reader/expect_expr_xs.cpp index 3d238ede..1f07d131 100644 --- a/src/reader/expect_expr_xs.cpp +++ b/src/reader/expect_expr_xs.cpp @@ -55,10 +55,14 @@ namespace xo { expect_expr_xs::on_def_token(const token_type & tk, parserstatemachine * p_psm) { - if (allow_defs_) + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + if (allow_defs_) { define_xs::start(p_psm); - else + } else { exprstate::on_def_token(tk, p_psm); + } } void @@ -101,6 +105,9 @@ namespace xo { expect_expr_xs::on_rightbrace_token(const token_type & tk, parserstatemachine * p_psm) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + if (cxl_on_rightbrace_) { auto self = p_psm->pop_exprstate(); @@ -116,7 +123,10 @@ namespace xo { expect_expr_xs::on_symbol_token(const token_type & tk, parserstatemachine * p_psm) { - /* todo: treat symbol as variable name */ + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("tk", tk)); /* various possibilities when looking for rhs expression: * diff --git a/src/reader/expect_symbol_xs.cpp b/src/reader/expect_symbol_xs.cpp index 27a94b64..0e01924e 100644 --- a/src/reader/expect_symbol_xs.cpp +++ b/src/reader/expect_symbol_xs.cpp @@ -28,6 +28,11 @@ namespace xo { expect_symbol_xs::on_symbol_token(const token_type & tk, parserstatemachine * p_psm) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("tk", tk)); + /* have to do pop first, before sending symbol to * the o.g. symbol-requester */ diff --git a/src/reader/exprstate.cpp b/src/reader/exprstate.cpp index 550e20d0..77480dcb 100644 --- a/src/reader/exprstate.cpp +++ b/src/reader/exprstate.cpp @@ -394,9 +394,17 @@ namespace xo { constexpr bool c_debug_flag = true; scope log(XO_DEBUG(c_debug_flag)); + const char * c_self_name = "exprstate::on_expr_with_semicolon"; + log && log(xtag("exstype", this->exs_type_), xtag("expr", expr)); + throw std::runtime_error + (tostr(c_self_name, + ": unexpected expression for parsing state", + xtag("expr", expr), + xtag("state", *this))); + assert(false); } /*on_expr_with_semicolon*/ diff --git a/src/reader/parserstatemachine.cpp b/src/reader/parserstatemachine.cpp index 814c3a3b..373e83e3 100644 --- a/src/reader/parserstatemachine.cpp +++ b/src/reader/parserstatemachine.cpp @@ -100,6 +100,19 @@ namespace xo { ->top_exprstate().on_semicolon_token(tk, this); } + void + parserstatemachine::on_operator_token(const token_type & tk) + { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + + log && log(xtag("tk", tk), + xtag("psm", *this)); + + this->p_stack_ + ->top_exprstate().on_operator_token(tk, this); + } + void parserstatemachine::on_leftbrace_token(const token_type & tk) { diff --git a/src/reader/progress_xs.cpp b/src/reader/progress_xs.cpp index 47bf2c04..40d857b6 100644 --- a/src/reader/progress_xs.cpp +++ b/src/reader/progress_xs.cpp @@ -332,6 +332,9 @@ namespace xo { progress_xs::on_operator_token(const token_type & tk, parserstatemachine * p_psm) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + constexpr const char * c_self_name = "progress_xs::on_operator_token"; if (op_type_ == optype::invalid) { From e40dc2daabf3d8f332c42b4de91fb5edfe4d4b41 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 10:16:03 -0500 Subject: [PATCH 1476/2524] xo-ordinaltree: use latest xo-cmake macros --- CMakeLists.txt | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ff3d8726..74ba5859 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,26 +6,16 @@ project(xo_ordinaltree VERSION 0.1) enable_language(CXX) # common XO macros (see github:Rconybea/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) -# ---------------------------------------------------------------- -# unit test setup - -enable_testing() -# enable code coverage for all executables+libraries -# (when configured with -DCODE_COVERAGE=ON) -# -add_code_coverage() -add_code_coverage_all_targets( - EXCLUDE - /nix/store/* - ${PROJECT_SOURCE_DIR}/utest/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings -# sets XO_COMPILE_OPTIONS -xo_toplevel_compile_options() +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) # ---------------------------------------------------------------- # output targets From d18e9afc1d43625700513aff4f2ed114618faf36 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 10:55:31 -0500 Subject: [PATCH 1477/2524] xo-ratio: update README.md for xo-build --- README.md | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d4104014..33a2a155 100644 --- a/README.md +++ b/README.md @@ -28,21 +28,34 @@ Relative to `boost::ratio`: - [github/rconybea/xo-indentlog](https://github.com/Rconybea/xo-indentlog) logging (used by unit tests) - [github/rconybea/xo-randomgen](https://github.com/Rconybea/xo-randomgen) rng (used by unit tests) -### clone xo-ratio +### copy repository locally +Using `xo-build` (provided by `xo-cmake`): +``` +$ xo-build --clone xo-ratio` +``` + +or equivalently: ``` $ cd ~/proj # for example $ git clone https://github.com/Rconybea/xo-ratio ``` ### build + install + +Using `xo-build`: +``` +$ xo-build --configure --build --install xo-ratio +``` + +or equivalently: ``` -$ cd xo-ratio $ PREFIX=/usr/local # for example $ BUILDDIR=.build # for example -$ mkdir ${BUILDDIR} -$ cmake --build .build -$ cmake --install .build +$ mkdir xo-ratio/${BUILDDIR} +$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-ratio -B xo-ratio/${BUILDDIR} +$ cmake --build xo-ratio/${BUILDDIR} +$ cmake --install xo-ratio/${BUILDDIR} ``` ### build with unit test coverage From c3115779979a08c221ff61cf6bb8f20e94b8b1a9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 11:55:10 -0500 Subject: [PATCH 1478/2524] xo-pyreflect: bugfix: track ns change xo::ref::rp -> xo::rp --- src/pyreflect/pyreflect.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index ac5ef86e..846e8cba 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -16,7 +16,6 @@ namespace xo { using xo::time::utc_nanos; using xo::ref::unowned_ptr; - using xo::ref::rp; namespace py = pybind11; namespace reflect { From ea8faf70328621f1469cae1dc1cfc0b7c4e73796 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 12:08:54 -0500 Subject: [PATCH 1479/2524] xo-printjson: build: update for latest xo-cmake macros --- CMakeLists.txt | 20 ++---------------- utest/CMakeLists.txt | 50 +------------------------------------------- 2 files changed, 3 insertions(+), 67 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b12b9f56..1ba80032 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,25 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(printjson VERSION 0.1) -enable_language(CXX) -# common XO cmake macros (see https://github.com/rconybea/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.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/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings @@ -31,8 +17,6 @@ set(PROJECT_CXX_FLAGS "") #set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -xo_toplevel_compile_options() - # ---------------------------------------------------------------- add_subdirectory(src/printjson) diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 0b2177e8..8b4e7828 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -3,56 +3,8 @@ set(SELF_EXE utest.printjson) set(SELF_SRCS printjson_utest_main.cpp PrintJson.test.cpp) -add_executable(${SELF_EXE} ${SELF_SRCS}) -xo_include_options2(${SELF_EXE}) - -add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) -target_code_coverage(${SELF_EXE} 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_EXE} PUBLIC -# ${PROJECT_SOURCE_DIR} -# ${PROJECT_BINARY_DIR}) - -# ---------------------------------------------------------------- -# dependencies on this codebase - +xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS}) xo_self_dependency(${SELF_EXE} printjson) - -# ---------------------------------------------------------------- -# dependencies on other codebases - xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) -# 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_EXE} PUBLIC ${CATCH_INCLUDE_DIR}) - -# supplied from xo_include_options2() -## ---------------------------------------------------------------- -## 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 From cac526a5170fa0c2356fc1978dfa41c87058d9d5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 12:09:06 -0500 Subject: [PATCH 1480/2524] xo-printjson: bugfix: adjust for upstream changes: xo::ref::rp -> xo::rp xo::quoted -> xo::quot new reflection metatype mt_function --- include/xo/printjson/PrintJson.hpp | 6 +++--- src/printjson/PrintJson.cpp | 12 +++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/include/xo/printjson/PrintJson.hpp b/include/xo/printjson/PrintJson.hpp index c0ddcf55..8f463c27 100644 --- a/include/xo/printjson/PrintJson.hpp +++ b/include/xo/printjson/PrintJson.hpp @@ -41,7 +41,7 @@ namespace xo { /* convenience -- shorthand for * .print(obj->self_tp(), p_os) */ - void print_obj(ref::rp const & obj, std::ostream * p_os) const; + void print_obj(rp const & obj, std::ostream * p_os) const; void provide_printer(TypeId id, std::unique_ptr p) { *(printer_map_.require(id)) = std::move(p); @@ -74,13 +74,13 @@ namespace xo { */ class PrintJsonSingleton { public: - static ref::rp instance(); + static rp instance(); private: /* we don't need this to be stored as pointer. * memory burned if unused will be one empty std::vector<> */ - static ref::rp s_instance; + static rp s_instance; }; /*PrintJsonSingleton*/ } /*namespace json*/ diff --git a/src/printjson/PrintJson.cpp b/src/printjson/PrintJson.cpp index 7df2d5b3..3d5ae34b 100644 --- a/src/printjson/PrintJson.cpp +++ b/src/printjson/PrintJson.cpp @@ -17,9 +17,8 @@ namespace xo { using xo::reflect::TypeDescr; using xo::reflect::TaggedPtr; using xo::reflect::TaggedRcptr; - using xo::print::quoted; + using xo::print::quot; using xo::time::iso8601; - using xo::ref::rp; using xo::xtag; namespace json { @@ -153,6 +152,13 @@ namespace xo { case Metatype::mt_struct: print_generic_struct(*this, tp, p_os); return; + case Metatype::mt_function: + /** new branch (added for xo-expression / xo-jit) **/ + (*p_os) << "canonical_name()) + << xtag("metatype", tp.td()->metatype()) + << ">"; + return; case Metatype::mt_invalid: case Metatype::mt_atomic: break; @@ -340,7 +346,7 @@ namespace xo { if (x) { /* TODO: escapes special characters */ - *p_os << quoted(*x); + *p_os << quot(*x); } else { report_internal_type_consistency_error(Reflect::require(), tp.td(), From e4088392b8b45e4224cdaed100c337db7d3ec4e8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 12:20:27 -0500 Subject: [PATCH 1481/2524] xo-pyprintjson: build: update to latest xo-cmake macros --- CMakeLists.txt | 22 ++++------------------ src/pyprintjson/CMakeLists.txt | 3 ++- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 603f7253..dd29fbc9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,25 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(xo_pyprintjson VERSION 1.0) -enable_language(CXX) -# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.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/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings (usually temporary) @@ -29,8 +15,6 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* set(PROJECT_CXX_FLAGS "") add_definitions(${PROJECT_CXX_FLAGS}) -xo_toplevel_compile_options() - # ---------------------------------------------------------------- # sources @@ -41,3 +25,5 @@ add_subdirectory(src/pyprintjson) # provide find_package() support xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/src/pyprintjson/CMakeLists.txt b/src/pyprintjson/CMakeLists.txt index a45f1b82..defff223 100644 --- a/src/pyprintjson/CMakeLists.txt +++ b/src/pyprintjson/CMakeLists.txt @@ -3,7 +3,8 @@ set(SELF_LIB xo_pyprintjson) set(SELF_SRCS pyprintjson.cpp) -xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +# ---------------------------------------------------------------- +xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} printjson) xo_pybind11_header_dependency(${SELF_LIB} xo_pyreflect) From 2ccc717579e5cf3afeb2c260d1dccff8aeb476a7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 12:20:44 -0500 Subject: [PATCH 1482/2524] xo-pyprintjson: bugfix: track ns change xo::ref::rp -> xo::rp --- src/pyprintjson/pyprintjson.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pyprintjson/pyprintjson.cpp b/src/pyprintjson/pyprintjson.cpp index f49d4b41..1e107f31 100644 --- a/src/pyprintjson/pyprintjson.cpp +++ b/src/pyprintjson/pyprintjson.cpp @@ -21,7 +21,6 @@ namespace xo { namespace json { using xo::reflect::SelfTagging; using xo::reflect::TaggedRcptr; - using xo::ref::rp; using xo::ref::unowned_ptr; PYBIND11_MODULE(PYPRINTJSON_MODULE_NAME(), m) { From 36797a419a695f8f0e9d526002fe50e7978a12d7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 12:26:41 -0500 Subject: [PATCH 1483/2524] xo-pyprintjson: build: use xo-cmake-config from xo-cmake --- cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From 6743b9ef1830cead15bbf0a378e616406e9ab173 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 12:29:49 -0500 Subject: [PATCH 1484/2524] xo-printjson: build: use xo-cmake-config from xo-cmake macros --- cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From 704f415239d8cb479ac523624ca6b291081e8d42 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:13:41 -0500 Subject: [PATCH 1485/2524] xo-callback: update to latest xo-cmake macros --- CMakeLists.txt | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6e1c8bc..17d73eb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,25 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(callback VERSION 0.1) -enable_language(CXX) -# common XO cmake macros (see proj/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) -# ---------------------------------------------------------------- -# unit test setup (for consistency with other xo libraries. no unit tests as of 10oct2023) - -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/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # common c++ settings @@ -31,8 +17,6 @@ set(PROJECT_CXX_FLAGS "") #set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -xo_toplevel_compile_options() - # ---------------------------------------------------------------- # sources @@ -40,7 +24,7 @@ add_subdirectory(src/callback) #add_subdirectory(utest) # ---------------------------------------------------------------- -# provide find_package() support to customers +# provide find_package() support xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) From fda578b55493f8b5d1ba786171d9596f62db56c3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:14:34 -0500 Subject: [PATCH 1486/2524] xo-callback: update to latest cmake bootstrap --- cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From 4e1849c726ce3e6addbeb1cc8b3b67051eebf833 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:24:07 -0500 Subject: [PATCH 1487/2524] xo-callback: bugfix: track ns change xo::ref::rp -> xo::rp --- include/xo/callback/CallbackSet.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/callback/CallbackSet.hpp b/include/xo/callback/CallbackSet.hpp index 9bba6514..cfc3047b 100644 --- a/include/xo/callback/CallbackSet.hpp +++ b/include/xo/callback/CallbackSet.hpp @@ -286,7 +286,7 @@ namespace xo { }; /*CallbackSetImpl*/ template - using RpCallbackSet = CallbackSetImpl>; + using RpCallbackSet = CallbackSetImpl>; /* like RpCallbackSet, * but also provides overload(s) for operator()(..) From 7d1e10c09ba1630e61c3b26c47f85c529ba4abd6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:26:15 -0500 Subject: [PATCH 1488/2524] xo-webutil: build: update to latest xo-cmake macros --- CMakeLists.txt | 19 ++----------------- cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 94fd52ed..76c8795a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,24 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(webutil VERSION 1.0) -enable_language(CXX) -# common XO cmake macros (see proj/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) -# ---------------------------------------------------------------- -# unit test setup (no unit tests yet, but want 'make tests' to do something) - -enable_testing() -# activate code coverage for all executables + libraries (when -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. -# -add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # common c++ settings @@ -30,8 +17,6 @@ set(PROJECT_CXX_FLAGS "") #set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -xo_toplevel_compile_options() - # ---------------------------------------------------------------- # sources diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From 8e75838950e3e61a9f63136efd9d013a95bea0b1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:26:25 -0500 Subject: [PATCH 1489/2524] xo-webutil: bugfix: track ns change xo::ref::rp -> xo::rp --- include/xo/webutil/StreamEndpointDescr.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/webutil/StreamEndpointDescr.hpp b/include/xo/webutil/StreamEndpointDescr.hpp index 53aa63c7..93a2e77b 100644 --- a/include/xo/webutil/StreamEndpointDescr.hpp +++ b/include/xo/webutil/StreamEndpointDescr.hpp @@ -15,7 +15,7 @@ namespace xo { namespace web { /* a function that creates an event subscription */ - using StreamSubscribeFn = std::function const & ws_sink)>; + using StreamSubscribeFn = std::function const & ws_sink)>; using StreamUnsubscribeFn = std::function; /* describes a stream endpoint From 9caa56f5e9fc4906967340a4d83d38ea361c08cd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:34:11 -0500 Subject: [PATCH 1490/2524] xo-pywebutil: build: update to latest xo-cmake macros --- CMakeLists.txt | 20 ++------------------ cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 22a91f72..9851b8cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,25 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(xo_pywebutil VERSION 0.1) -enable_language(CXX) -# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.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/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings (usually temporary) @@ -29,8 +15,6 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* set(PROJECT_CXX_FLAGS "") add_definitions(${PROJECT_CXX_FLAGS}) -xo_toplevel_compile_options() - # ---------------------------------------------------------------- # sources diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From 578b3d724a3534de031d10cde092859547aaac32 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:34:23 -0500 Subject: [PATCH 1491/2524] xo-pywebutil: bugfix: track ns change xo::ref::rp -> xo::rp --- src/pywebutil/pywebutil.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pywebutil/pywebutil.cpp b/src/pywebutil/pywebutil.cpp index dbaa8ef6..8618eba9 100644 --- a/src/pywebutil/pywebutil.cpp +++ b/src/pywebutil/pywebutil.cpp @@ -9,7 +9,6 @@ namespace xo { //using xo::web::Alist; using xo::web::HttpEndpointDescr; - using xo::ref::rp; //using xo::time::utc_nanos; namespace py = pybind11; From b257f891404f8f74a4baee846dea6303533fc741 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:46:51 -0500 Subject: [PATCH 1492/2524] xo-reactor: build: update to latest xo-cmake macros --- CMakeLists.txt | 20 ++------------------ cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ utest/CMakeLists.txt | 27 +-------------------------- 3 files changed, 30 insertions(+), 50 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c4d8f9f..0f8848ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,25 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(reactor VERSION 0.1) -enable_language(CXX) -# common XO cmake macros (see proj/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.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/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings @@ -31,8 +17,6 @@ set(PROJECT_CXX_FLAGS "") #set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -xo_toplevel_compile_options() - # ---------------------------------------------------------------- add_subdirectory(src/reactor) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 1f538cd2..e621b6ca 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -3,34 +3,9 @@ set(SELF_EXE utest.reactor) set(SELF_SRCS Sink.test.cpp PollingReactor.test.cpp reactor_utest_main.cpp) -add_executable(${SELF_EXE} ${SELF_SRCS}) -xo_include_options2(${SELF_EXE}) - -add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) -target_code_coverage(${SELF_EXE} AUTO ALL) - -# ---------------------------------------------------------------- -# internal dependency (on this codebase) - +xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS}) xo_self_dependency(${SELF_EXE} reactor) xo_dependency(${SELF_EXE} randomgen) - -# ---------------------------------------------------------------- -# external dependencies - xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) -# should be getting this via xo_include_options2() - -## ---------------------------------------------------------------- -## 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 From 000ed7b957b00d2dbad0d5ba64896b5b462d4f99 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:47:32 -0500 Subject: [PATCH 1493/2524] xo-reactor: bugfix: TypeDescr::typeinfo() -> native_type_info() --- include/xo/reactor/Sink.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/xo/reactor/Sink.hpp b/include/xo/reactor/Sink.hpp index 2ba561c7..51d71268 100644 --- a/include/xo/reactor/Sink.hpp +++ b/include/xo/reactor/Sink.hpp @@ -85,7 +85,7 @@ namespace xo { xtag("required_hashcode", typeid(Sink1).hash_code()), xtag("required_name", typeid(Sink1).name()), xtag("src_hashcode", src_hashcode), - xtag("sink_hashcode", sink->sink_ev_type()->typeinfo()->hash_code()) + xtag("sink_hashcode", sink->sink_ev_type()->native_typeinfo()->hash_code()) #ifdef DEBUG_EVENT_TYPEINFO , xtag("sink_hashcode", sink->item_typeinfo()->hash_code()) , xtag("sink_parent_hashcode", sink_parent_typeinfo->hash_code()) From aa40c8ac7517b52d20cd9232ccf619be97a429fb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:48:03 -0500 Subject: [PATCH 1494/2524] xo-reactor: bugfix: track ns change xo::ref::rp -> xo::rp --- include/xo/reactor/AbstractEventProcessor.hpp | 2 +- include/xo/reactor/AbstractSink.hpp | 2 +- include/xo/reactor/AbstractSource.hpp | 4 ++-- include/xo/reactor/EventSource.hpp | 2 +- include/xo/reactor/EventTimeFn2.hpp | 4 ++-- include/xo/reactor/FifoQueue.hpp | 8 ++++---- include/xo/reactor/PollingReactor.hpp | 2 +- include/xo/reactor/PolyAdapterSink.hpp | 8 ++++---- include/xo/reactor/ReactorSource.hpp | 2 +- include/xo/reactor/Sink.hpp | 6 +++--- src/reactor/AbstractEventProcessor.cpp | 1 - src/reactor/AbstractSource.cpp | 3 --- utest/PollingReactor.test.cpp | 1 - utest/Sink.test.cpp | 1 - 14 files changed, 20 insertions(+), 26 deletions(-) diff --git a/include/xo/reactor/AbstractEventProcessor.hpp b/include/xo/reactor/AbstractEventProcessor.hpp index 820ab0ae..aa04a1c8 100644 --- a/include/xo/reactor/AbstractEventProcessor.hpp +++ b/include/xo/reactor/AbstractEventProcessor.hpp @@ -25,7 +25,7 @@ namespace xo { /* find all event processors ep reachable from x (i.e. downstream from x). * report each such ep exactly once */ - static std::vector> map_network(ref::rp const & x); + static std::vector> map_network(rp const & x); /* visit direct downstream consumers c[i] of this event processor. * call ep(c[i]) for each such consumer. diff --git a/include/xo/reactor/AbstractSink.hpp b/include/xo/reactor/AbstractSink.hpp index e855eda8..46e2bf0b 100644 --- a/include/xo/reactor/AbstractSink.hpp +++ b/include/xo/reactor/AbstractSink.hpp @@ -59,7 +59,7 @@ namespace xo { * with a function thats calls a .notify_xxx() method * on this Sink */ - virtual void attach_source(ref::rp const & src) = 0; + virtual void attach_source(rp const & src) = 0; /* accept incoming event, given by tagged pointer */ virtual void notify_ev_tp(TaggedPtr const & ev_tp) = 0; diff --git a/include/xo/reactor/AbstractSource.hpp b/include/xo/reactor/AbstractSource.hpp index 4d995ab2..d98938ed 100644 --- a/include/xo/reactor/AbstractSource.hpp +++ b/include/xo/reactor/AbstractSource.hpp @@ -64,7 +64,7 @@ namespace xo { /* set .trace_sim_flag */ virtual void set_debug_sim_flag(bool x) = 0; - virtual CallbackId attach_sink(ref::rp const & sink) = 0; + virtual CallbackId attach_sink(rp const & sink) = 0; virtual void detach_sink(CallbackId id) = 0; /* endpoint for a websocket subscriber; @@ -92,7 +92,7 @@ namespace xo { std::uint64_t deliver_all(); }; /*AbstractSource*/ - using AbstractSourcePtr = ref::rp; + using AbstractSourcePtr = rp; } /*namespace reactor*/ } /*namespace xo*/ diff --git a/include/xo/reactor/EventSource.hpp b/include/xo/reactor/EventSource.hpp index 055c7d6b..bb08eca9 100644 --- a/include/xo/reactor/EventSource.hpp +++ b/include/xo/reactor/EventSource.hpp @@ -15,7 +15,7 @@ namespace xo { using CallbackId = fn::CallbackId; public: - virtual CallbackId add_callback(ref::rp const & cb) = 0; + virtual CallbackId add_callback(rp const & cb) = 0; virtual void remove_callback(CallbackId id) = 0; }; /*EventSource*/ diff --git a/include/xo/reactor/EventTimeFn2.hpp b/include/xo/reactor/EventTimeFn2.hpp index 6d2b0170..9f68eb00 100644 --- a/include/xo/reactor/EventTimeFn2.hpp +++ b/include/xo/reactor/EventTimeFn2.hpp @@ -20,10 +20,10 @@ namespace xo { }; template - class EventTimeFn> { + class EventTimeFn> { public: using utc_nanos = xo::time::utc_nanos; - using event_t = xo::ref::rp; + using event_t = xo::rp; public: static utc_nanos event_tm(event_t const & ev) { return ev->tm(); } diff --git a/include/xo/reactor/FifoQueue.hpp b/include/xo/reactor/FifoQueue.hpp index 175836b3..1d1439d2 100644 --- a/include/xo/reactor/FifoQueue.hpp +++ b/include/xo/reactor/FifoQueue.hpp @@ -29,7 +29,7 @@ namespace xo { using utc_nanos = xo::time::utc_nanos; public: - static ref::rp make(EvTimeFn evtm_fn = EvTimeFn()) { return new FifoQueue(evtm_fn); } + static rp make(EvTimeFn evtm_fn = EvTimeFn()) { return new FifoQueue(evtm_fn); } // ----- inherited from Sink1 ----- @@ -141,8 +141,8 @@ namespace xo { virtual bool debug_sim_flag() const override { return debug_sim_flag_; } virtual void set_debug_sim_flag(bool x) override { this->debug_sim_flag_ = x; } - virtual CallbackId attach_sink(ref::rp const & sink) override { - ref::rp native_sink + virtual CallbackId attach_sink(rp const & sink) override { + rp native_sink = EventSink::require_native("FifoQueue::attach_sink", sink); if (native_sink) { @@ -168,7 +168,7 @@ namespace xo { // ----- inherited from EventSource ----- - virtual CallbackId add_callback(ref::rp const & cb) override { + virtual CallbackId add_callback(rp const & cb) override { return this->cb_set_.add_callback(cb); } diff --git a/include/xo/reactor/PollingReactor.hpp b/include/xo/reactor/PollingReactor.hpp index 8cbe2c2f..be4dd028 100644 --- a/include/xo/reactor/PollingReactor.hpp +++ b/include/xo/reactor/PollingReactor.hpp @@ -13,7 +13,7 @@ namespace xo { class PollingReactor : public Reactor { public: /* named ctor idiom */ - static ref::rp make() { return new PollingReactor(); } + static rp make() { return new PollingReactor(); } // ----- inherited from Reactor ----- diff --git a/include/xo/reactor/PolyAdapterSink.hpp b/include/xo/reactor/PolyAdapterSink.hpp index 60ec4ccf..e5e56c69 100644 --- a/include/xo/reactor/PolyAdapterSink.hpp +++ b/include/xo/reactor/PolyAdapterSink.hpp @@ -29,10 +29,10 @@ namespace xo { public: /* named ctor idiom */ - static ref::rp make(ref::rp poly_sink) { + static rp make(rp poly_sink) { //xo::scope lscope("PolyAdapterSink::make"); - ref::rp retval(new PolyAdapterSink(poly_sink)); + rp retval(new PolyAdapterSink(poly_sink)); //lscope.log("adapter", (void*)retval.get()); @@ -80,11 +80,11 @@ namespace xo { } /*display*/ private: - PolyAdapterSink(ref::rp poly_sink) : poly_sink_{std::move(poly_sink)} {} + PolyAdapterSink(rp poly_sink) : poly_sink_{std::move(poly_sink)} {} private: /* mandate: .poly_sink.allow_polymorphic_source() is true */ - ref::rp poly_sink_; + rp poly_sink_; }; /*PolyAdapterSink*/ } /*namespace reactor*/ } /*namespace xo*/ diff --git a/include/xo/reactor/ReactorSource.hpp b/include/xo/reactor/ReactorSource.hpp index 8b217a4b..bdcc9db4 100644 --- a/include/xo/reactor/ReactorSource.hpp +++ b/include/xo/reactor/ReactorSource.hpp @@ -123,7 +123,7 @@ namespace xo { uint64_t online_advance_until(utc_nanos tm, bool replay_flag); }; /*ReactorSource*/ - using ReactorSourcePtr = ref::rp; + using ReactorSourcePtr = rp; } /*namespace reactor*/ } /*namespace xo*/ diff --git a/include/xo/reactor/Sink.hpp b/include/xo/reactor/Sink.hpp index 51d71268..dab326f9 100644 --- a/include/xo/reactor/Sink.hpp +++ b/include/xo/reactor/Sink.hpp @@ -40,8 +40,8 @@ namespace xo { /* convenience: convert abstract sink to Sink1*, * or throw */ - static ref::rp> require_native(std::string_view caller, - ref::rp const & sink) + static rp> require_native(std::string_view caller, + rp const & sink) { using xo::scope; using xo::xtag; @@ -112,7 +112,7 @@ namespace xo { /* Sink1 only allows source providing T */ virtual bool allow_polymorphic_source() const override { return false; } - virtual void attach_source(ref::rp const & src) override { + virtual void attach_source(rp const & src) override { src->attach_sink(this); } /*attach_source*/ diff --git a/src/reactor/AbstractEventProcessor.cpp b/src/reactor/AbstractEventProcessor.cpp index 4b1bcc52..55939ab1 100644 --- a/src/reactor/AbstractEventProcessor.cpp +++ b/src/reactor/AbstractEventProcessor.cpp @@ -6,7 +6,6 @@ #include namespace xo { - using ref::rp; using ref::brw; using xo::tostr; using std::uint32_t; diff --git a/src/reactor/AbstractSource.cpp b/src/reactor/AbstractSource.cpp index 973ec4c6..bbad859d 100644 --- a/src/reactor/AbstractSource.cpp +++ b/src/reactor/AbstractSource.cpp @@ -8,9 +8,6 @@ namespace xo { using xo::web::StreamEndpointDescr; using xo::reactor::AbstractSink; - using xo::ref::rp; - //using xo::scope; - //using xo::tostr; namespace reactor { StreamEndpointDescr diff --git a/utest/PollingReactor.test.cpp b/utest/PollingReactor.test.cpp index 106acaf6..bcdf861f 100644 --- a/utest/PollingReactor.test.cpp +++ b/utest/PollingReactor.test.cpp @@ -13,7 +13,6 @@ namespace xo { using xo::reactor::PollingReactor; using xo::reactor::FifoQueue; using xo::reactor::SinkToFunction; - using xo::ref::rp; using xo::time::timeutil; using xo::time::seconds; using xo::time::utc_nanos; diff --git a/utest/Sink.test.cpp b/utest/Sink.test.cpp index 160530a0..524b821e 100644 --- a/utest/Sink.test.cpp +++ b/utest/Sink.test.cpp @@ -13,7 +13,6 @@ namespace xo { using xo::reactor::SinkEndpoint; using xo::reactor::SinkToConsole; using xo::time::utc_nanos; - using xo::ref::rp; namespace { class TestSink : public SinkEndpoint { From e3a53d10e6b6a6c7246f0ea72df5da4df7150301 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:56:48 -0500 Subject: [PATCH 1495/2524] xo-reactor: bugfix: xo::ref::rp -> xo::rp --- include/xo/reactor/DirectSourcePtr.hpp | 2 +- include/xo/reactor/EventStore.hpp | 10 +++++----- include/xo/reactor/SecondarySource.hpp | 8 ++++---- include/xo/reactor/Sink.hpp | 2 +- src/reactor/Sink.cpp | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/xo/reactor/DirectSourcePtr.hpp b/include/xo/reactor/DirectSourcePtr.hpp index 8947a74a..f68eb27c 100644 --- a/include/xo/reactor/DirectSourcePtr.hpp +++ b/include/xo/reactor/DirectSourcePtr.hpp @@ -13,7 +13,7 @@ namespace xo { LastReducer>>; - /* use when Event is ref::rp for some T */ + /* use when Event is rp for some T */ template using DirectSourcePtr = SecondarySource const & pjson, + virtual void http_snapshot(rp const & pjson, std::ostream * p_os) const = 0; /* http endpoint; generates http output for this eventstore */ - virtual HttpEndpointDescr http_endpoint_descr(ref::rp const & pjson, + virtual HttpEndpointDescr http_endpoint_descr(rp const & pjson, std::string const & url_prefix) const { /* important that lambda contains its own rp; * reference to stack will not do */ - ref::rp pjson_rp = pjson; + rp pjson_rp = pjson; auto http_fn = ([this, pjson_rp] (std::string const & /*uri*/, @@ -118,7 +118,7 @@ namespace xo { using Alist = xo::web::Alist; using HttpEndpointDescr = xo::web::HttpEndpointDescr; - static ref::rp make() { return new EventStoreImpl(); } + static rp make() { return new EventStoreImpl(); } /* visit most recent n events in this store. * returns #of events actually visited @@ -206,7 +206,7 @@ namespace xo { virtual std::uint32_t size() const override { return tree_.size(); } /* write http snapshot of current state to *p_os */ - virtual void http_snapshot(ref::rp const & pjson, std::ostream * p_os) const override { + virtual void http_snapshot(rp const & pjson, std::ostream * p_os) const override { using xo::reflect::Reflect; /* visit last 100 events; diff --git a/include/xo/reactor/SecondarySource.hpp b/include/xo/reactor/SecondarySource.hpp index 3b10de63..5b186b83 100644 --- a/include/xo/reactor/SecondarySource.hpp +++ b/include/xo/reactor/SecondarySource.hpp @@ -29,7 +29,7 @@ namespace xo { public: ~SecondarySource() = default; - static ref::rp make() { return new SecondarySource(); } + static rp make() { return new SecondarySource(); } /* last event delivered from this source -- * i.e. event in most recent call to .deliver_one_aux() @@ -114,7 +114,7 @@ namespace xo { // ----- inherited from EventSource ----- - virtual CallbackId add_callback(ref::rp const & cb) override { + virtual CallbackId add_callback(rp const & cb) override { return this->cb_set_.add_callback(cb); } /*add_callback*/ @@ -195,8 +195,8 @@ namespace xo { virtual bool debug_sim_flag() const override { return debug_sim_flag_; } virtual void set_debug_sim_flag(bool x) override { this->debug_sim_flag_ = x; } - virtual CallbackId attach_sink(ref::rp const & sink) override { - ref::rp native_sink + virtual CallbackId attach_sink(rp const & sink) override { + rp native_sink = EventSink::require_native("SecondarySource::attach_sink", sink); if (native_sink) { diff --git a/include/xo/reactor/Sink.hpp b/include/xo/reactor/Sink.hpp index dab326f9..a5f29a03 100644 --- a/include/xo/reactor/Sink.hpp +++ b/include/xo/reactor/Sink.hpp @@ -213,7 +213,7 @@ namespace xo { #ifdef NOT_USING class TemporaryTest { public: - static ref::rp>> realization_printer(); + static rp>> realization_printer(); }; /*TemporaryTest*/ #endif } /*namespace reactor*/ diff --git a/src/reactor/Sink.cpp b/src/reactor/Sink.cpp index 1dbcdb62..78f281e8 100644 --- a/src/reactor/Sink.cpp +++ b/src/reactor/Sink.cpp @@ -6,7 +6,7 @@ namespace xo { namespace reactor { #ifdef NOT_USING - ref::rp>> + rp>> TemporaryTest::realization_printer() { return new SinkToConsole>(); From a3e3f381c2e091c2a33fbb15fed0c137b8a1061a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:57:59 -0500 Subject: [PATCH 1496/2524] xo-pyreactor: build: update to latest xo-cmake macros --- CMakeLists.txt | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 773425bb..6d47faed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,25 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(xo_pyreactor VERSION 1.0) -enable_language(CXX) -# common XO cmake macros (see github.com:Rconybea/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.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/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings (usually temporary) @@ -29,8 +15,6 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* set(PROJECT_CXX_FLAGS "") add_definitions(${PROJECT_CXX_FLAGS}) -xo_toplevel_compile_options() - # ---------------------------------------------------------------- # sources From d02fa163246d63954ef783351c456fc5f794452f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:58:13 -0500 Subject: [PATCH 1497/2524] xo-pyreactor: update to latest cmake bootstrap --- cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From 50b8d529a764dea6b48f3df33a8de7a1e8412009 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 13:58:36 -0500 Subject: [PATCH 1498/2524] xo-pyreactor: xo::ref::rp -> xo::rp --- src/pyreactor/CMakeLists.txt | 1 - src/pyreactor/pyreactor.cpp | 11 +++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/pyreactor/CMakeLists.txt b/src/pyreactor/CMakeLists.txt index 686e0b69..fa30e71d 100644 --- a/src/pyreactor/CMakeLists.txt +++ b/src/pyreactor/CMakeLists.txt @@ -4,6 +4,5 @@ set(SELF_LIB xo_pyreactor) set(SELF_SRCS pyreactor.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) - xo_pybind11_dependency(${SELF_LIB} reactor) xo_pybind11_header_dependency(${SELF_LIB} xo_pyprintjson) diff --git a/src/pyreactor/pyreactor.cpp b/src/pyreactor/pyreactor.cpp index a5120f96..f28e610d 100644 --- a/src/pyreactor/pyreactor.cpp +++ b/src/pyreactor/pyreactor.cpp @@ -22,7 +22,6 @@ namespace xo { using xo::json::PrintJsonSingleton; using xo::fn::CallbackId; using xo::ref::Refcount; - using xo::ref::rp; using xo::time::utc_nanos; using xo::tostr; namespace py = pybind11; @@ -42,7 +41,7 @@ namespace xo { py::class_(m, "CallbackId"); py::class_>(m, "AbstractEventProcessor") + xo::rp>(m, "AbstractEventProcessor") .def_property("name", &AbstractEventProcessor::name, &AbstractEventProcessor::set_name) @@ -53,7 +52,7 @@ namespace xo { py::class_>(m, "AbstractSource") + xo::rp>(m, "AbstractSource") .def_property_readonly("source_ev_type", &AbstractSource::source_ev_type) .def_property_readonly("is_volatile", &AbstractSource::is_volatile) .def_property_readonly("n_out_ev", &AbstractSource::n_out_ev) @@ -68,7 +67,7 @@ namespace xo { py::class_>(m, "AbstractSink") + xo::rp>(m, "AbstractSink") //.cdef("__repr__", &AbstractSink::display_string) .def_property_readonly("sink_ev_type", &AbstractSink::sink_ev_type) .def_property_readonly("n_in_ev", &AbstractSink::n_in_ev) @@ -76,7 +75,7 @@ namespace xo { py::class_> + xo::rp> (m, "ReactorSource") .def_property_readonly("is_empty", &ReactorSource::is_empty) .def_property_readonly("is_nonempty", &ReactorSource::is_nonempty) @@ -131,7 +130,7 @@ namespace xo { py::class_>, AbstractSink, - xo::ref::rp>>> + xo::rp>>> (m, "SinkToConsole"); #endif } /*pyreactor*/ From f10dd12dc17a57895350929925820e45bbf78d75 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 14:05:05 -0500 Subject: [PATCH 1499/2524] xo-simulator: build: update to latest xo-cmake macros --- CMakeLists.txt | 20 ++------------------ cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b9020fd0..624c63d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,25 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(simulator VERSION 1.0) -enable_language(CXX) -# common XO cmake macros (see proj/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.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/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings @@ -31,8 +17,6 @@ set(PROJECT_CXX_FLAGS "") #set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -xo_toplevel_compile_options() - # ---------------------------------------------------------------- add_subdirectory(src/simulator) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From 3452e68e0cb96c8afcbd80aa769279639cf9b54e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 14:05:16 -0500 Subject: [PATCH 1500/2524] xo-simulator: bugfix: xo::ref::rp -> xo::rp --- include/xo/simulator/Simulator.hpp | 14 +++++++------- src/simulator/Simulator.cpp | 2 +- src/simulator/SourceTimestamp.cpp | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/xo/simulator/Simulator.hpp b/include/xo/simulator/Simulator.hpp index d7a0baa1..da87b555 100644 --- a/include/xo/simulator/Simulator.hpp +++ b/include/xo/simulator/Simulator.hpp @@ -24,23 +24,23 @@ namespace xo { public: ReentrantSimulatorCmd() = default; ReentrantSimulatorCmd(SimulatorCmdEnum cmd, - ref::rp const & src) + rp const & src) : cmd_{cmd}, src_{src} {} - static ReentrantSimulatorCmd notify_source_primed(ref::rp const & src) { + static ReentrantSimulatorCmd notify_source_primed(rp const & src) { return ReentrantSimulatorCmd(NotifySourcePrimed, src); } - static ReentrantSimulatorCmd complete_add_source(ref::rp const & src) { + static ReentrantSimulatorCmd complete_add_source(rp const & src) { return ReentrantSimulatorCmd(CompleteAddSource, src); } - static ReentrantSimulatorCmd complete_remove_source(ref::rp const & src) { + static ReentrantSimulatorCmd complete_remove_source(rp const & src) { return ReentrantSimulatorCmd(CompleteRemoveSource, src); } SimulatorCmdEnum cmd() const { return cmd_; } - ref::rp const & src() const { return src_; } + rp const & src() const { return src_; } private: /* NotifySourcePrimed: deferred Simulator.notify_source_primed(.src) @@ -49,7 +49,7 @@ namespace xo { */ SimulatorCmdEnum cmd_ = NotifySourcePrimed; /* if .cmd=NotifySourcePrimed|CompleteAddSource|CompleteRemoveSource: reactor source */ - ref::rp src_; + rp src_; }; /*ReentrantSimulatorCmd*/ /* Generic simulator @@ -74,7 +74,7 @@ namespace xo { public: ~Simulator(); - static ref::rp make(utc_nanos t0); + static rp make(utc_nanos t0); /* value of .t0() is estabished in ctor. * it will not change except across call to .advance_one() diff --git a/src/simulator/Simulator.cpp b/src/simulator/Simulator.cpp index 9835b1ed..3d64c1b4 100644 --- a/src/simulator/Simulator.cpp +++ b/src/simulator/Simulator.cpp @@ -30,7 +30,7 @@ namespace xo { Simulator * sim_ = nullptr; }; /*RaiiDeliveryWork*/ - ref::rp + rp Simulator::make(utc_nanos t0) { return new Simulator(t0); } /*make*/ diff --git a/src/simulator/SourceTimestamp.cpp b/src/simulator/SourceTimestamp.cpp index c3dca025..f425beeb 100644 --- a/src/simulator/SourceTimestamp.cpp +++ b/src/simulator/SourceTimestamp.cpp @@ -17,7 +17,7 @@ namespace xo { { os << "(src_)); + os << xtag("src", rp(src_)); os << ">"; } /*display*/ From 31a544dd03e27a07c10a45f2ac654478003df6c2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 14:13:06 -0500 Subject: [PATCH 1501/2524] xo-pysimulator: build: update to latest xo-cmake macros --- CMakeLists.txt | 23 +++------------------- cmake/xo-bootstrap-macros.cmake | 35 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 cmake/xo-bootstrap-macros.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 25398891..03088e39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,25 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(xo_pysimulator VERSION 0.1) -enable_language(CXX) -# common XO cmake macros (see https://github.com:rconybea/xo-cmake.git) -include(xo_macros/xo-project-macros) +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.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/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings (usually temporary) @@ -29,8 +15,6 @@ add_code_coverage_all_targets(EXCLUDE /nix/store/* ${PROJECT_SOURCE_DIR}/utest/* set(PROJECT_CXX_FLAGS "") add_definitions(${PROJECT_CXX_FLAGS}) -xo_toplevel_compile_options() - # ---------------------------------------------------------------- # sources @@ -41,4 +25,3 @@ add_subdirectory(src/pysimulator) # provide find_package() support xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) - diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From eca5806575aa4ab59be2f26c94916818c716e7da Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 14:17:54 -0500 Subject: [PATCH 1502/2524] xo-distribution: build: update to latest xo-cmake --- CMakeLists.txt | 7 ++----- cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ utest/CMakeLists.txt | 8 +------- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6c5fcb1..12f76055 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,16 +4,13 @@ cmake_minimum_required(VERSION 3.10) project(xo_distribution VERSION 1.0) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) -xo_cxx_toplevel_options() +xo_cxx_toplevel_options3() #add_subdirectory(example) add_subdirectory(src/distribution) # note refcnt dep -> not header-only add_subdirectory(utest) xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) - -# ---------------------------------------------------------------- - -#install(Targets ex1 DESTINATION bin/distribution/example) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 652bf644..e768c09a 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -6,14 +6,8 @@ set(SELF_SRCS Normal.test.cpp Uniform.test.cpp) -add_executable(${SELF_EXE} ${SELF_SRCS}) -xo_include_options2(${SELF_EXE}) - -add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) -target_code_coverage(${SELF_EXE} AUTO ALL) - +xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS}) xo_self_dependency(${SELF_EXE} xo_distribution) - xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) # end CMakeLists.txt From 5d80fa4499176291d715fe3a958749244fb62d9a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 14:18:10 -0500 Subject: [PATCH 1503/2524] xo-distribution: bugfix: xo::ref::rp -> xo::rp --- include/xo/distribution/Normal.hpp | 2 +- include/xo/distribution/Uniform.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/xo/distribution/Normal.hpp b/include/xo/distribution/Normal.hpp index 6d4d88e1..f3ab1498 100644 --- a/include/xo/distribution/Normal.hpp +++ b/include/xo/distribution/Normal.hpp @@ -14,7 +14,7 @@ namespace xo { Normal() = default; /* N(0,1): mean 0, sdev 1 */ - static ref::rp unit() { return new Normal(); } + static rp unit() { return new Normal(); } /* normal probability density: * diff --git a/include/xo/distribution/Uniform.hpp b/include/xo/distribution/Uniform.hpp index e40c62a6..d701e846 100644 --- a/include/xo/distribution/Uniform.hpp +++ b/include/xo/distribution/Uniform.hpp @@ -11,7 +11,7 @@ namespace xo { public: Uniform(double lo, double hi) : lo_(lo), hi_(hi) {} - static ref::rp unit() { return new Uniform(0.0, 1.0); } + static rp unit() { return new Uniform(0.0, 1.0); } static double density_impl(double lo, double hi, double x) { if (x <= lo) From 036ca5d817f3f6a92b95e44fa4bb5d7f065a7478 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 14:26:02 -0500 Subject: [PATCH 1504/2524] xo-distribution: bugfix: xo::ref::rp -> xo::rp --- include/xo/distribution/ExplicitDist.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/xo/distribution/ExplicitDist.hpp b/include/xo/distribution/ExplicitDist.hpp index 516413e1..abb673bb 100644 --- a/include/xo/distribution/ExplicitDist.hpp +++ b/include/xo/distribution/ExplicitDist.hpp @@ -179,13 +179,13 @@ namespace xo { using iterator = detail::ExplicitDistIterator; public: - static ref::rp make(Domain bucket_dx, Domain ref_value) { + static rp make(Domain bucket_dx, Domain ref_value) { return new ExplicitDist(bucket_dx, ref_value); } /* create distribution with n buckets of width bucket_dx, * covering range [ref_value, ref_value + n * bucket_dx] */ - static ref::rp make_n(size_t n, Domain bucket_dx, Domain ref_value) { + static rp make_n(size_t n, Domain bucket_dx, Domain ref_value) { return new ExplicitDist(n, bucket_dx, ref_value); } From 07be5ae7ec78f71b63ed21eff71be6ee72fe5cec Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 14:27:07 -0500 Subject: [PATCH 1505/2524] xo-pydistribution: build: update to latest xo-cmake macros --- CMakeLists.txt | 17 ++++++++++++++++- cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b4de072..3389b1a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,10 +4,25 @@ cmake_minimum_required(VERSION 3.10) project(xo_pydistribution VERSION 1.0) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) -xo_cxx_toplevel_options() +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- +# sources add_subdirectory(src/pydistribution) +# ---------------------------------------------------------------- +# provide find_package() support + xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From a142a0044c75984848735fbfdc24e6dfb88d01cc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 14:27:22 -0500 Subject: [PATCH 1506/2524] xo-pydistribution: bugfix: xo::ref::rp -> xo::rp --- src/pydistribution/CMakeLists.txt | 2 ++ src/pydistribution/pydistribution.cpp | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pydistribution/CMakeLists.txt b/src/pydistribution/CMakeLists.txt index 356ba92e..389ae251 100644 --- a/src/pydistribution/CMakeLists.txt +++ b/src/pydistribution/CMakeLists.txt @@ -3,6 +3,8 @@ set(SELF_LIB xo_pydistribution) set(SELF_SRCS pydistribution.cpp) +# ---------------------------------------------------------------- + xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} xo_distribution) xo_pybind11_dependency(${SELF_LIB} xo_pyutil) diff --git a/src/pydistribution/pydistribution.cpp b/src/pydistribution/pydistribution.cpp index 8104ccaf..58793950 100644 --- a/src/pydistribution/pydistribution.cpp +++ b/src/pydistribution/pydistribution.cpp @@ -12,7 +12,6 @@ namespace xo { using xo::distribution::Normal; using xo::distribution::Distribution; using xo::distribution::ExplicitDist; - using xo::ref::rp; namespace sim { namespace py = pybind11; From 41f5ee3f8890e16236fcfb857385c1b4a7017440 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 14:40:04 -0500 Subject: [PATCH 1507/2524] xo-reflect: bugfix: missing PointerTdx.n_child_fixed() --- CMakeLists.txt | 2 ++ include/xo/reflect/pointer/PointerTdx.hpp | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index c2e8b74d..be7c1d7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,3 +25,5 @@ add_subdirectory(utest) # provide find_package() support xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/include/xo/reflect/pointer/PointerTdx.hpp b/include/xo/reflect/pointer/PointerTdx.hpp index 037d3ee4..5e59a9d9 100644 --- a/include/xo/reflect/pointer/PointerTdx.hpp +++ b/include/xo/reflect/pointer/PointerTdx.hpp @@ -23,6 +23,12 @@ namespace xo { virtual Metatype metatype() const override { return Metatype::mt_pointer; } virtual uint32_t n_child(void * object) const override = 0; + + /* number of children unknown at compile time. + * null-pointer -> 0, non-null pointer -> 1 + */ + virtual uint32_t n_child_fixed() const override { return 0; /*unknown*/ } + virtual TaggedPtr child_tp(uint32_t i, void * object) const override = 0; /* (forbidden) */ virtual std::string const & struct_member_name(uint32_t i) const override; From 5dce303fe10ce05c0b3af2a922694f8d64208ba2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 18:26:10 -0500 Subject: [PATCH 1508/2524] xo-reflect: bugfix: missing RefPointerTdx.fixed_child_td() --- include/xo/reflect/pointer/PointerTdx.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/include/xo/reflect/pointer/PointerTdx.hpp b/include/xo/reflect/pointer/PointerTdx.hpp index 5e59a9d9..4484c6ed 100644 --- a/include/xo/reflect/pointer/PointerTdx.hpp +++ b/include/xo/reflect/pointer/PointerTdx.hpp @@ -28,7 +28,6 @@ namespace xo { * null-pointer -> 0, non-null pointer -> 1 */ virtual uint32_t n_child_fixed() const override { return 0; /*unknown*/ } - virtual TaggedPtr child_tp(uint32_t i, void * object) const override = 0; /* (forbidden) */ virtual std::string const & struct_member_name(uint32_t i) const override; @@ -36,7 +35,7 @@ namespace xo { // ----- RefPointerTdx ----- - /* xo::ref::intrusive_ptr for some T */ + /* Pointer = xo::ref::intrusive_ptr for some T */ template class RefPointerTdx : public PointerTdx { public: @@ -58,6 +57,10 @@ namespace xo { return 0; } /*n_child*/ + virtual TypeDescrBase * fixed_child_td(uint32_t /*i*/) const override { + return EstablishTypeDescr::establish(); + } + virtual TaggedPtr child_tp(uint32_t i, void * object) const override { using xo::tostr; using xo::xtag; From 4a5512bc164fa904137e92de8ce0bfd068cb5d20 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 18:28:06 -0500 Subject: [PATCH 1509/2524] xo-process: build: upgrade to latest xo-cmake macros --- CMakeLists.txt | 20 ++------------------ cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ utest/CMakeLists.txt | 14 +------------- 3 files changed, 30 insertions(+), 37 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a10d628..3cf358bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,25 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(process VERSION 1.0) -enable_language(CXX) -# common XO cmake macros (see proj/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.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/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings @@ -31,8 +17,6 @@ set(PROJECT_CXX_FLAGS "") #set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -xo_toplevel_compile_options() - # ---------------------------------------------------------------- add_subdirectory(src/process) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index 39bd3870..a3d5585b 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -8,20 +8,8 @@ set(SELF_SRCS RealizationSource.test.cpp process_utest_main.cpp) -add_executable(${SELF_EXE} ${SELF_SRCS}) -xo_include_options2(${SELF_EXE}) - -add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) -target_code_coverage(${SELF_EXE} AUTO ALL) - -# ---------------------------------------------------------------- -# internal dependencies (on this codebase) - +xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS}) xo_self_dependency(${SELF_EXE} process) - -# ---------------------------------------------------------------- -# external dependencies - xo_dependency(${SELF_EXE} simulator) xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) From 7cfc560f026d4b432b965eb58510366d49b2c2b7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 18:28:22 -0500 Subject: [PATCH 1510/2524] xo-process: bugfix: track xo::ref::rp -> xo::rp --- include/xo/process/AbstractRealization.hpp | 2 +- include/xo/process/BrownianMotion.hpp | 6 +++--- include/xo/process/ExpProcess.hpp | 4 ++-- include/xo/process/LogNormalProcess.hpp | 4 ++-- include/xo/process/Realizable2Process.hpp | 8 ++++---- include/xo/process/Realization.hpp | 4 ++-- include/xo/process/Realization2.hpp | 10 ++++----- include/xo/process/RealizationSource.hpp | 24 +++++++++++----------- include/xo/process/RealizationTracer.hpp | 8 ++++---- include/xo/process/UpxToConsole.hpp | 2 +- src/process/UpxToConsole.cpp | 2 +- utest/RealizationSource.test.cpp | 3 +-- 12 files changed, 38 insertions(+), 39 deletions(-) diff --git a/include/xo/process/AbstractRealization.hpp b/include/xo/process/AbstractRealization.hpp index e664bb0a..b87e20ac 100644 --- a/include/xo/process/AbstractRealization.hpp +++ b/include/xo/process/AbstractRealization.hpp @@ -12,7 +12,7 @@ namespace xo { namespace process { class AbstractRealization : public reflect::SelfTagging { public: - virtual ref::rp stochastic_process() const = 0; + virtual rp stochastic_process() const = 0; }; /*AbstractRealization*/ } /*namespace process*/ } /*namespace xo*/ diff --git a/include/xo/process/BrownianMotion.hpp b/include/xo/process/BrownianMotion.hpp index 317b7ec3..1ec1f5cf 100644 --- a/include/xo/process/BrownianMotion.hpp +++ b/include/xo/process/BrownianMotion.hpp @@ -36,7 +36,7 @@ namespace xo { // ----- needed by Realization2 ----- - virtual ref::rp> make_realization() override = 0; + virtual rp> make_realization() override = 0; // ----- inherited from StochasticProcess ----- @@ -83,7 +83,7 @@ namespace xo { * seed. initialize pseudorandom-number generator */ template - static ref::rp> make(utc_nanos t0, + static rp> make(utc_nanos t0, double sdev, Seed const & seed) { @@ -121,7 +121,7 @@ namespace xo { // ----- inherited from Realizable2Process<> ----- - virtual ref::rp> make_realization() override { + virtual rp> make_realization() override { rstate_type rs0 = std::make_pair(this->t0(), this->t0_value()); diff --git a/include/xo/process/ExpProcess.hpp b/include/xo/process/ExpProcess.hpp index a49313bb..8cf6f526 100644 --- a/include/xo/process/ExpProcess.hpp +++ b/include/xo/process/ExpProcess.hpp @@ -26,7 +26,7 @@ namespace xo { using TaggedRcptr = reflect::TaggedRcptr; public: - static ref::rp make(double scale, + static rp make(double scale, ref::brw> exp_proc) { return new ExpProcess(scale, exp_proc); } /*make*/ @@ -95,7 +95,7 @@ namespace xo { */ double scale_ = 1.0; /* exponentiate this process */ - ref::rp> exponent_process_; + rp> exponent_process_; }; /*ExpProcess*/ } /*namespace process*/ } /*namespace xo*/ diff --git a/include/xo/process/LogNormalProcess.hpp b/include/xo/process/LogNormalProcess.hpp index a7a3ae1a..36d6cf49 100644 --- a/include/xo/process/LogNormalProcess.hpp +++ b/include/xo/process/LogNormalProcess.hpp @@ -17,10 +17,10 @@ namespace xo { public: /* log-normal process starting at (t0, x0) */ template - static ref::rp make(utc_nanos t0, double x0, + static rp make(utc_nanos t0, double x0, double sdev, Seed const & seed) { - ref::rp> bm + rp> bm = BrownianMotion::make(t0, sdev, seed); return ExpProcess::make(x0 /*scale*/, bm); diff --git a/include/xo/process/Realizable2Process.hpp b/include/xo/process/Realizable2Process.hpp index b05e889f..5a3effd3 100644 --- a/include/xo/process/Realizable2Process.hpp +++ b/include/xo/process/Realizable2Process.hpp @@ -17,7 +17,7 @@ namespace xo { * one of its own paths * - p provides methods to implement such unfolding: * - .make_realization() :: Realization2 - * create new realization of p. + * create new realization of p. * - .rstate_sample(t1,rs0) :: time x Rstate -> Rstate * given runstate rs0 representing process state at some time t0, * sample process at time t1, with t0<=t1 @@ -28,7 +28,7 @@ namespace xo { * - .rstate_insample(t1,rs0,rs2) :: time x Rstate x Rstate -> Rstate * given runstates rs0, rs2 representing process state at two times t0 class Realizable2Process : public StochasticProcess { public: - virtual ref::rp> make_realization() = 0; + virtual rp> make_realization() = 0; /* make_rstate() will be used to establish nested state when a process is used * as input to a transforming process (ex: ExpProcess). * in that context the outer process' realization state will @@ -50,7 +50,7 @@ namespace xo { // Rstate rstate_init() const; // void rstate_sample_implace(utc_nanos t1, Rstate * p_rs0 const; }; /*Realizable2Process*/ - + } /*namespace process*/ } /*namespace xo*/ diff --git a/include/xo/process/Realization.hpp b/include/xo/process/Realization.hpp index 571b7b5a..35ced2df 100644 --- a/include/xo/process/Realization.hpp +++ b/include/xo/process/Realization.hpp @@ -33,7 +33,7 @@ namespace xo { using KnownRange = decltype(std::views::all(KnownMap())); public: - static ref::rp make(ref::brw> p) { + static rp make(ref::brw> p) { return new Realization(p); } /*make*/ @@ -58,7 +58,7 @@ namespace xo { private: /* stochastic process from which this realization is sampled */ - ref::rp> process_; + rp> process_; /* process value (for this realization) has been established (sampled) * at each time t in {.known_map[].first} diff --git a/include/xo/process/Realization2.hpp b/include/xo/process/Realization2.hpp index 499d815b..1edae93a 100644 --- a/include/xo/process/Realization2.hpp +++ b/include/xo/process/Realization2.hpp @@ -46,13 +46,13 @@ namespace xo { using nanos = xo::time::nanos; public: - ProcessRealization2(Rstate const & rstate, ref::rp const & process) + ProcessRealization2(Rstate const & rstate, rp const & process) : rstate_{rstate}, process_{process} {} - ProcessRealization2(Rstate && rstate, ref::rp const & process) + ProcessRealization2(Rstate && rstate, rp const & process) : rstate_{std::move(rstate)}, process_{process} {} Rstate const & rstate() const { return rstate_; } - ref::rp const & process() const { return process_; } + rp const & process() const { return process_; } /* sample process at point .rstate.tk + dt * Require: @@ -64,7 +64,7 @@ namespace xo { // ----- inherited from AbstractRealization ----- - virtual ref::rp stochastic_process() const override { + virtual rp stochastic_process() const override { return process_; } /*stochastic_process*/ @@ -81,7 +81,7 @@ namespace xo { /* process (set of paths + probability measure); * *this coordinates with .process to constructively samples one such path */ - ref::rp process_; + rp process_; }; /*ProcessRealization2*/ } /*namespace process*/ diff --git a/include/xo/process/RealizationSource.hpp b/include/xo/process/RealizationSource.hpp index f5087942..932da915 100644 --- a/include/xo/process/RealizationSource.hpp +++ b/include/xo/process/RealizationSource.hpp @@ -39,8 +39,8 @@ namespace xo { xtag("p", this)); } /*dtor*/ - static ref::rp - make(ref::rp> const & tracer, + static rp + make(rp> const & tracer, nanos ev_interval_dt, EventSink const & ev_sink) { @@ -60,7 +60,7 @@ namespace xo { } /*make*/ #ifdef NOT_IN_USE - static ref::rp make(ref::rp> tracer, + static rp make(rp> tracer, nanos ev_interval_dt, EventSink && ev_sink) { @@ -142,7 +142,7 @@ namespace xo { return 1; } /*deliver_one*/ - virtual CallbackId attach_sink(ref::rp const & /*sink*/) override { + virtual CallbackId attach_sink(rp const & /*sink*/) override { /* see RealizationSource */ assert(false); return CallbackId(); @@ -168,13 +168,13 @@ namespace xo { } protected: - RealizationSourceBase(ref::rp> const & tracer, + RealizationSourceBase(rp> const & tracer, nanos ev_interval_dt, EventSink const & ev_sink) : tracer_{tracer}, ev_sink_{std::move(ev_sink)}, ev_interval_dt_{ev_interval_dt} {} - RealizationSourceBase(ref::rp> const & tracer, + RealizationSourceBase(rp> const & tracer, nanos ev_interval_dt, EventSink && ev_sink) : tracer_{tracer}, @@ -192,7 +192,7 @@ namespace xo { /* counts lifetime #of events */ uint32_t n_out_ev_ = 0; /* produces events representing realized stochastic-process values */ - ref::rp> tracer_; + rp> tracer_; /* send stochastic-process events to this sink */ EventSink ev_sink_; /* discretize process using this interval: @@ -218,13 +218,13 @@ namespace xo { using nanos = xo::time::nanos; public: - static ref::rp> make(ref::rp> const & tracer, + static rp> make(rp> const & tracer, nanos ev_interval_dt) { return new RealizationSource(tracer, ev_interval_dt); } /*make*/ - CallbackId add_callback(ref::rp> const & cb) { + CallbackId add_callback(rp> const & cb) { return this->ev_sink_addr()->add_callback(cb); } /*add_callback*/ @@ -238,7 +238,7 @@ namespace xo { * .add_callback(sink) <--> .attach_sink(sink) * .remove_callback(sink) <--> .detach_sink(sink) */ - virtual CallbackId attach_sink(ref::rp const & sink) override { + virtual CallbackId attach_sink(rp const & sink) override { /* ------- * WARNING * ------- @@ -265,7 +265,7 @@ namespace xo { //scope lscope(c_self_name); //lscope.log(xtag("T", reflect::type_name())); - ref::rp> event_sink + rp> event_sink = reactor::Sink1::require_native(c_self_name, sink); return this->add_callback(event_sink); @@ -296,7 +296,7 @@ namespace xo { } /*visit_direct_consumers*/ private: - RealizationSource(ref::rp> const & tracer, + RealizationSource(rp> const & tracer, nanos ev_interval_dt) : RealizationSourceBase make(ref::rp const & p) { + static rp make(rp const & p) { return new RealizationTracer(p); } @@ -40,7 +40,7 @@ namespace xo { utc_nanos current_tm() const { return current_.first; } /* value of this path at time t */ T const & current_value() const { return current_.second; } - ref::rp const & process() const { return process_; } + rp const & process() const { return process_; } /* sample with fixed time: * - advance to time t+dt, where t=.current_tm() @@ -95,7 +95,7 @@ namespace xo { #endif private: - RealizationTracer(ref::rp const & p) + RealizationTracer(rp const & p) : current_(event_type(p->t0(), p->t0_value())), process_(p) {} private: @@ -103,7 +103,7 @@ namespace xo { event_type current_; /* develop a sampled realization of this stochastic process */ - ref::rp process_; + rp process_; }; /*RealizationTracer*/ } /*namespace process*/ diff --git a/include/xo/process/UpxToConsole.hpp b/include/xo/process/UpxToConsole.hpp index 50c259fd..650de79c 100644 --- a/include/xo/process/UpxToConsole.hpp +++ b/include/xo/process/UpxToConsole.hpp @@ -17,7 +17,7 @@ namespace xo { public: UpxToConsole(); - static ref::rp make(); + static rp make(); }; /*UpxToConsole*/ } /*namespace process*/ } /*namespace xo*/ diff --git a/src/process/UpxToConsole.cpp b/src/process/UpxToConsole.cpp index f78a3499..b6bf08ef 100644 --- a/src/process/UpxToConsole.cpp +++ b/src/process/UpxToConsole.cpp @@ -4,7 +4,7 @@ namespace xo { namespace process { - ref::rp + rp UpxToConsole::make() { return new UpxToConsole(); diff --git a/utest/RealizationSource.test.cpp b/utest/RealizationSource.test.cpp index 5d8e98c1..ae6aeb0c 100644 --- a/utest/RealizationSource.test.cpp +++ b/utest/RealizationSource.test.cpp @@ -20,7 +20,6 @@ namespace xo { using xo::process::BrownianMotion; using xo::rng::xoshiro256ss; using xo::reactor::SinkToConsole; - using xo::ref::rp; using xo::time::timeutil; using xo::time::seconds; using xo::time::utc_nanos; @@ -98,7 +97,7 @@ namespace xo { log && log("create brownian motion process 'bm'.."); - ref::rp> bm + rp> bm = BrownianMotion::make(t0, 0.30 /*sdev -- annualized volatility*/, 12345678UL /*seed*/); From 5068fd493f66bc86952c34963d3c96d7c620fd04 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 19:02:52 -0500 Subject: [PATCH 1511/2524] xo-pyprocess: build: update to latest xo-cmake macros --- CMakeLists.txt | 16 +++++++++++++++- cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d53e2ca6..e2693076 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,10 +4,24 @@ cmake_minimum_required(VERSION 3.10) project(xo_pyprocess VERSION 0.1) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) -xo_cxx_toplevel_options() +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings (usually temporary) + +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- add_subdirectory(src/pyprocess) +# ---------------------------------------------------------------- +# provide find_package() support + xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From c4af12c62582473c8321f5946194b31afdcf96f5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 19:03:07 -0500 Subject: [PATCH 1512/2524] xo-pyprocess: bugfix: track xo::ref::rp -> xo::rp --- src/pyprocess/pyprocess.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/pyprocess/pyprocess.cpp b/src/pyprocess/pyprocess.cpp index 56b5b1f8..e5b51dcc 100644 --- a/src/pyprocess/pyprocess.cpp +++ b/src/pyprocess/pyprocess.cpp @@ -33,7 +33,6 @@ namespace xo { using xo::time::utc_nanos; using xo::rng::Seed; using xo::rng::xoshiro256ss; - using xo::ref::rp; namespace py = pybind11; namespace process { @@ -75,7 +74,7 @@ namespace xo { py::arg("start_tm"), py::arg("start_value"), py::arg("annual_volatility")); py::class_, - xo::ref::rp>>(m, "StochasticProcess") + xo::rp>>(m, "StochasticProcess") .def_property_readonly("t0", &StochasticProcess::t0) .def_property_readonly("t0_value", &StochasticProcess::t0_value) .def("exterior_sample", &StochasticProcess::exterior_sample) @@ -83,12 +82,12 @@ namespace xo { py::class_, StochasticProcess, - xo::ref::rp>>(m, "BrownianMotion"); + xo::rp>>(m, "BrownianMotion"); //.def("exterior_sample", &BrownianMotion::exterior_sample) //.def("__repr__", &BrownianMotion::display_string); py::class_, - xo::ref::rp>(m, "ExpProcess") + xo::rp>(m, "ExpProcess") .def_property_readonly("exponent_process", [](ExpProcess & self) { return self.exponent_process().promote(); @@ -98,7 +97,7 @@ namespace xo { &RealizationTracer::make); py::class_, - xo::ref::rp>>(m, "RealizationTracer"); + xo::rp>>(m, "RealizationTracer"); /* e.g. * import datetime as dt @@ -107,7 +106,7 @@ namespace xo { * s=pyprocess.make_realization_source(ebm, dt.timedelta(seconds=1)) */ m.def("make_realization_source", - [](xo::ref::rp> p, + [](xo::rp> p, xo::time::nanos sample_dt) { auto tracer = RealizationTracer::make(p); @@ -126,7 +125,7 @@ namespace xo { py::class_, reactor::ReactorSource, - xo::ref::rp>>(m, "RealizationSource") + xo::rp>>(m, "RealizationSource") .def_property_readonly("current_ev", &RealizationSource::current_ev); py::class_ Date: Sat, 14 Sep 2024 19:07:58 -0500 Subject: [PATCH 1513/2524] xo-statistics: build: update to latest xo-cmake macros --- CMakeLists.txt | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1732dfa4..c01852a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,22 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(xo_statistics VERSION 1.0) -enable_language(CXX) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.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. -# -add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # bespoke (usually temporary) c++ settings @@ -27,19 +16,6 @@ set(PROJECT_CXX_FLAGS "") #set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -# ---------------------------------------------------------------- -# common include paths etc. - -xo_toplevel_compile_options() - -# ---------------------------------------------------------------- -# external dependencies -# -# set CMAKE_INSTALL_PREFIX to analog of /usr -# to use .cmake assistants from /usr/lib/cmake/indentlog -# -# xo_dependency(..) - # ---------------------------------------------------------------- #add_subdirectory(example) From ae49d8896a531d34562ba0b080cf471bbb2c6b9a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 19:08:16 -0500 Subject: [PATCH 1514/2524] xo-statistics: build: upgrade xo-cmake bootstrap --- cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From e140c6d2858a89b6d1402e44e4e2fbca9d9a5fe6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 19:15:11 -0500 Subject: [PATCH 1515/2524] xo-kalmanfilter: build: update to latest xo-cmake macros --- CMakeLists.txt | 20 ++---------------- cmake/xo-bootstrap-macros.cmake | 33 ++++++++++++++++++++++++------ utest/CMakeLists.txt | 36 +-------------------------------- 3 files changed, 30 insertions(+), 59 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c83fec9..a49c8cb2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,25 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(xo_kalmanfilter VERSION 1.0) -enable_language(CXX) -# common XO cmake macros (see proj/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.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/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings @@ -31,8 +17,6 @@ set(PROJECT_CXX_FLAGS "") #set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -xo_toplevel_compile_options() - # ---------------------------------------------------------------- add_subdirectory(src/kalmanfilter) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index a118b8a7..1cd562ad 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -6,11 +6,7 @@ set(SELF_SRCS KalmanFilter.test.cpp filter_utest_main.cpp) -add_executable(${SELF_EXE} ${SELF_SRCS}) -xo_include_options2(${SELF_EXE}) - -add_test(NAME ${SELF_EXE} COMMAND ${SELF_EXE}) -target_code_coverage(${SELF_EXE} AUTO ALL) +xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS}) # ---------------------------------------------------------------- # create convenience symlink from build dir back to canned data. @@ -20,38 +16,8 @@ target_code_coverage(${SELF_EXE} AUTO ALL) file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/utest/utestdata) file(CREATE_LINK ${PROJECT_SOURCE_DIR}/utest/utestdata/filter ${PROJECT_BINARY_DIR}/utest/utestdata/filter SYMBOLIC) -# ---------------------------------------------------------------- -# generic project dependency - -# PROJECT_SOURCE_DIR: -# so we can for example write -# #include "logutil/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 -# xo_self_dependency(${SELF_EXE} xo_kalmanfilter) - -# ---------------------------------------------------------------- -# internal dependencies: logutil, ... - xo_dependency(${SELF_EXE} xo_statistics) - -# ---------------------------------------------------------------- -# external dependencies: catch2.. - xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) -# ---------------------------------------------------------------- -# 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 From 2ced8429c0c7fbd1b644349283e0a0654dddec61 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 19:15:47 -0500 Subject: [PATCH 1516/2524] xo-kalmanfilter: bugfix: track xo::ref::rp -> xo::rp --- include/xo/kalmanfilter/KalmanFilter.hpp | 6 +-- .../xo/kalmanfilter/KalmanFilterEngine.hpp | 32 +++++++------- include/xo/kalmanfilter/KalmanFilterInput.hpp | 6 +-- .../KalmanFilterInputCallback.hpp | 2 +- .../KalmanFilterInputToConsole.hpp | 4 +- .../KalmanFilterOutputCallback.hpp | 2 +- include/xo/kalmanfilter/KalmanFilterSpec.hpp | 12 ++--- include/xo/kalmanfilter/KalmanFilterState.hpp | 18 ++++---- .../KalmanFilterStateToConsole.hpp | 2 +- include/xo/kalmanfilter/KalmanFilterStep.hpp | 20 ++++----- include/xo/kalmanfilter/KalmanFilterSvc.hpp | 10 ++--- src/kalmanfilter/KalmanFilter.cpp | 2 +- src/kalmanfilter/KalmanFilterEngine.cpp | 44 +++++++++---------- src/kalmanfilter/KalmanFilterInput.cpp | 4 +- .../KalmanFilterInputToConsole.cpp | 2 +- src/kalmanfilter/KalmanFilterState.cpp | 4 +- .../KalmanFilterStateToConsole.cpp | 2 +- src/kalmanfilter/KalmanFilterStep.cpp | 14 +++--- src/kalmanfilter/KalmanFilterSvc.cpp | 4 +- utest/KalmanFilter.test.cpp | 10 ++--- 20 files changed, 100 insertions(+), 100 deletions(-) diff --git a/include/xo/kalmanfilter/KalmanFilter.hpp b/include/xo/kalmanfilter/KalmanFilter.hpp index 0df87a61..896b19ca 100644 --- a/include/xo/kalmanfilter/KalmanFilter.hpp +++ b/include/xo/kalmanfilter/KalmanFilter.hpp @@ -85,7 +85,7 @@ namespace xo { utc_nanos tm() const { return state_ext_->tm(); } KalmanFilterSpec const & filter_spec() const { return filter_spec_; } KalmanFilterStep const & step() const { return step_; } - ref::rp const & state_ext() const { return state_ext_; } + rp const & state_ext() const { return state_ext_; } /* notify kalman filter with input for time t(k+1) = input_kp1.tkp1() * Require: input.tkp1() >= .current_tm() @@ -95,7 +95,7 @@ namespace xo { * - .filter_spec_k, .step_k, .state_k updated * for observations in input_kp1 */ - void notify_input(ref::rp const & input_kp1); + void notify_input(rp const & input_kp1); void display(std::ostream & os) const; std::string display_string() const; @@ -112,7 +112,7 @@ namespace xo { /* filter state as of most recent observation; * result of applying KalmanFilterEngine::step() to contents of .step */ - ref::rp state_ext_; + rp state_ext_; }; /*KalmanFilter*/ inline std::ostream & diff --git a/include/xo/kalmanfilter/KalmanFilterEngine.hpp b/include/xo/kalmanfilter/KalmanFilterEngine.hpp index ce21bf3c..5ac6ef9a 100644 --- a/include/xo/kalmanfilter/KalmanFilterEngine.hpp +++ b/include/xo/kalmanfilter/KalmanFilterEngine.hpp @@ -39,8 +39,8 @@ namespace xo { * retval.x = x(k+1|k) * retval.P = P(k+1|k) */ - static ref::rp extrapolate(utc_nanos tkp1, - ref::rp const & sk, + static rp extrapolate(utc_nanos tkp1, + rp const & sk, KalmanFilterTransition const & Fk); /* compute kalman gain matrix for filter step t(k) -> t(k+1) @@ -59,7 +59,7 @@ namespace xo { * Hkp1. relates model state to observable variables, * for step t(k) -> t(k+1) */ - static MatrixXd kalman_gain(ref::rp const & skp1_ext, + static MatrixXd kalman_gain(rp const & skp1_ext, KalmanFilterObservable const & Hkp1); /* compute kalman gain for a single observation z(k)[j]. @@ -80,7 +80,7 @@ namespace xo { * amount of innovation in observable #j, i.e. for difference between * expected and actual value for that observable. */ - static VectorXd kalman_gain1(ref::rp const & skp1_ext, + static VectorXd kalman_gain1(rp const & skp1_ext, KalmanFilterObservable const & Hkp1, uint32_t j); @@ -98,9 +98,9 @@ namespace xo { * * return new filter state+cov */ - static ref::rp correct(ref::rp const & skp1_ext, + static rp correct(rp const & skp1_ext, KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1); + rp const & zkp1); /* correct extrapolated filter state for observation * of j'th filter observable z(k+1)[j] @@ -108,9 +108,9 @@ namespace xo { * Can use this when observation errors are uncorrelated * (i.e. observation error matrix R is diagonal) */ - static ref::rp correct1(ref::rp const & skp1_ext, + static rp correct1(rp const & skp1_ext, KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1, + rp const & zkp1, uint32_t j); /* step filter from t(k) -> t(k+1) @@ -123,11 +123,11 @@ namespace xo { * H (coupling matrix), R (error covar matrix) * zkp1. observations z(k+1) for time t(k+1) */ - static ref::rp step(utc_nanos tkp1, - ref::rp const & sk, + static rp step(utc_nanos tkp1, + rp const & sk, KalmanFilterTransition const & Fk, KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1); + rp const & zkp1); /* step filter from t(k) -> tk(k+1) * same as @@ -136,7 +136,7 @@ namespace xo { * step_spec. encapsulates Fk (transition-related params) * and Q (system noise covar matrix) */ - static ref::rp step(KalmanFilterStep const & step_spec); + static rp step(KalmanFilterStep const & step_spec); /* step filter from t(k) -> t(k+1) * @@ -150,11 +150,11 @@ namespace xo { * j. identifies a single filter observable -- * step will only consume observation z(k+1)[j] */ - static ref::rp step1(utc_nanos tkp1, - ref::rp const & sk, + static rp step1(utc_nanos tkp1, + rp const & sk, KalmanFilterTransition const & Fk, KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1, + rp const & zkp1, uint32_t j); /* step filter from t(k) -> t(k+1) @@ -176,7 +176,7 @@ namespace xo { * j. identifies a single filter observable -- * step will only consume observation z(k+1)[j] */ - static ref::rp step1(KalmanFilterStep const & step_spec, + static rp step1(KalmanFilterStep const & step_spec, uint32_t j); }; /*KalmanFilterEngine*/ } /*namespace kalman*/ diff --git a/include/xo/kalmanfilter/KalmanFilterInput.hpp b/include/xo/kalmanfilter/KalmanFilterInput.hpp index 70fee5af..9319ce58 100644 --- a/include/xo/kalmanfilter/KalmanFilterInput.hpp +++ b/include/xo/kalmanfilter/KalmanFilterInput.hpp @@ -32,13 +32,13 @@ namespace xo { public: KalmanFilterInput() = default; - static ref::rp make(utc_nanos tkp1, + static rp make(utc_nanos tkp1, VectorXb const & presence, VectorXd const & z, VectorXd const & Rd); /* create input, with all presence bits set + not using Rd */ - static ref::rp make_present(utc_nanos tkp1, + static rp make_present(utc_nanos tkp1, VectorXd const & z); /* reflect KalmanFilterInput object representation */ @@ -106,7 +106,7 @@ namespace xo { VectorXd Rd_; }; /*KalmanFilterInput*/ - using KalmanFilterInputPtr = ref::rp; + using KalmanFilterInputPtr = rp; inline std::ostream & operator<<(std::ostream & os, KalmanFilterInput const & x) diff --git a/include/xo/kalmanfilter/KalmanFilterInputCallback.hpp b/include/xo/kalmanfilter/KalmanFilterInputCallback.hpp index df86a16b..d89c6b68 100644 --- a/include/xo/kalmanfilter/KalmanFilterInputCallback.hpp +++ b/include/xo/kalmanfilter/KalmanFilterInputCallback.hpp @@ -7,7 +7,7 @@ namespace xo { namespace kalman { - using KalmanFilterInputCallback = reactor::Sink1>; + using KalmanFilterInputCallback = reactor::Sink1>; } /*namespace kalman*/ } /*namespace xo*/ diff --git a/include/xo/kalmanfilter/KalmanFilterInputToConsole.hpp b/include/xo/kalmanfilter/KalmanFilterInputToConsole.hpp index 7d81226a..bdc00e10 100644 --- a/include/xo/kalmanfilter/KalmanFilterInputToConsole.hpp +++ b/include/xo/kalmanfilter/KalmanFilterInputToConsole.hpp @@ -8,12 +8,12 @@ namespace xo { namespace kalman { class KalmanFilterInputToConsole - : public xo::reactor::SinkToConsole> + : public xo::reactor::SinkToConsole> { public: KalmanFilterInputToConsole() = default; - static ref::rp make(); + static rp make(); virtual void display(std::ostream & os) const; //virtual std::string display_string() const; diff --git a/include/xo/kalmanfilter/KalmanFilterOutputCallback.hpp b/include/xo/kalmanfilter/KalmanFilterOutputCallback.hpp index cd858606..69cfb4bc 100644 --- a/include/xo/kalmanfilter/KalmanFilterOutputCallback.hpp +++ b/include/xo/kalmanfilter/KalmanFilterOutputCallback.hpp @@ -8,7 +8,7 @@ namespace xo { namespace kalman { /* callback for consuming kalman filter output */ - using KalmanFilterOutputCallback = reactor::Sink1>; + using KalmanFilterOutputCallback = reactor::Sink1>; } /*namespace kalman*/ } /*namespace xo*/ diff --git a/include/xo/kalmanfilter/KalmanFilterSpec.hpp b/include/xo/kalmanfilter/KalmanFilterSpec.hpp index 306f298a..3bcca6ad 100644 --- a/include/xo/kalmanfilter/KalmanFilterSpec.hpp +++ b/include/xo/kalmanfilter/KalmanFilterSpec.hpp @@ -40,15 +40,15 @@ namespace xo { * } */ using MkStepFn = std::function const & sk, + (rp const & sk, KalmanFilterInputPtr const & zkp1)>; public: - explicit KalmanFilterSpec(ref::rp s0, MkStepFn mkstepfn) + explicit KalmanFilterSpec(rp s0, MkStepFn mkstepfn) : start_ext_{std::move(s0)}, mk_step_fn_{std::move(mkstepfn)} {} - ref::rp const & start_ext() const { return start_ext_; } + rp const & start_ext() const { return start_ext_; } /* get step parameters (i.e. matrices F, Q, H, R) * for step t(k) -> t(k+1). * @@ -56,8 +56,8 @@ namespace xo { * - to allow time stepping to be observation-driven * - to allow for selective outlier removal */ - KalmanFilterStep make_step(ref::rp const & sk, - ref::rp const & zkp1) { + KalmanFilterStep make_step(rp const & sk, + rp const & zkp1) { return this->mk_step_fn_(sk, zkp1); } /*make_step*/ @@ -66,7 +66,7 @@ namespace xo { private: /* starting state */ - ref::rp start_ext_; + rp start_ext_; /* creates kalman filter step object on demand; * step object specifies inputs to 1 step in discrete diff --git a/include/xo/kalmanfilter/KalmanFilterState.hpp b/include/xo/kalmanfilter/KalmanFilterState.hpp index 504def9c..fe814b9a 100644 --- a/include/xo/kalmanfilter/KalmanFilterState.hpp +++ b/include/xo/kalmanfilter/KalmanFilterState.hpp @@ -24,8 +24,8 @@ namespace xo { using uint32_t = std::uint32_t; public: - static ref::rp make(); - static ref::rp make(uint32_t k, + static rp make(); + static rp make(uint32_t k, utc_nanos tk, VectorXd x, MatrixXd P, @@ -100,18 +100,18 @@ namespace xo { using int32_t = std::int32_t; public: - static ref::rp make(); - static ref::rp make(uint32_t k, + static rp make(); + static rp make(uint32_t k, utc_nanos tk, VectorXd x, MatrixXd P, KalmanFilterTransition transition, MatrixXd K, int32_t j, - ref::rp zk); + rp zk); /* create state object for initial filter state */ - static ref::rp initial(utc_nanos t0, + static rp initial(utc_nanos t0, VectorXd x0, MatrixXd P0); @@ -120,7 +120,7 @@ namespace xo { int32_t observable() const { return j_; } MatrixXd const & gain() const { return K_; } - ref::rp const & zk() const { return zk_; } + rp const & zk() const { return zk_; } virtual void display(std::ostream & os) const override; @@ -137,7 +137,7 @@ namespace xo { KalmanFilterTransition transition, MatrixXd K, int32_t j, - ref::rp zk); + rp zk); private: /* if -1: not used; @@ -155,7 +155,7 @@ namespace xo { /* input leading to state k. * empty for initial state (i.e. when .k is 0) */ - ref::rp zk_; + rp zk_; }; /*KalamnFilterStateExt*/ } /*namespace filter*/ } /*namespace xo*/ diff --git a/include/xo/kalmanfilter/KalmanFilterStateToConsole.hpp b/include/xo/kalmanfilter/KalmanFilterStateToConsole.hpp index 9d072035..4f86b574 100644 --- a/include/xo/kalmanfilter/KalmanFilterStateToConsole.hpp +++ b/include/xo/kalmanfilter/KalmanFilterStateToConsole.hpp @@ -13,7 +13,7 @@ namespace xo { public: KalmanFilterStateToConsole() = default; - static ref::rp make(); + static rp make(); virtual void display(std::ostream & os) const; //virtual std::string display_string() const; diff --git a/include/xo/kalmanfilter/KalmanFilterStep.hpp b/include/xo/kalmanfilter/KalmanFilterStep.hpp index 432ac570..781b63a6 100644 --- a/include/xo/kalmanfilter/KalmanFilterStep.hpp +++ b/include/xo/kalmanfilter/KalmanFilterStep.hpp @@ -55,16 +55,16 @@ namespace xo { public: KalmanFilterStep() = default; - KalmanFilterStep(ref::rp state, + KalmanFilterStep(rp state, KalmanFilterTransition model, KalmanFilterObservable obs, - ref::rp zkp1) + rp zkp1) : KalmanFilterStepBase(model, obs), state_{std::move(state)}, input_{std::move(zkp1)} {} - ref::rp const & state() const { return state_; } - ref::rp const & input() const { return input_; } + rp const & state() const { return state_; } + rp const & input() const { return input_; } utc_nanos tkp1() const { return input_->tkp1(); } @@ -74,14 +74,14 @@ namespace xo { * P(k+1|k) * does not use the t(k+1) observations .input.z */ - ref::rp extrapolate() const; + rp extrapolate() const; /* compute kalman gain matrix K(k+1) * given extrapolated t(k+1) state skp1_ext = {x(k+1|k), P(k+1|k)} * * note that .state() != skp1_ext; .state() reports {x(k), P(k)} */ - MatrixXd gain(ref::rp const & skp1_ext) const; + MatrixXd gain(rp const & skp1_ext) const; /* compute kalman gain vector K(k+1) * given extrapolated t(k+1) state skp1_ext = {x(k+1|k), P(k+1|k)}, @@ -90,18 +90,18 @@ namespace xo { * just computing the gain vector. i'th member of gain vector * gives effect of innovation on i'th member of kalman filter state. */ - VectorXd gain1(ref::rp const & skp1_ext, + VectorXd gain1(rp const & skp1_ext, uint32_t j) const; /* compute correction to extrapolated filter state {x(k+1|k), P(k+1|k)}, * for observation z(k+1) = .input.z() */ - ref::rp correct(ref::rp const & skp1_ext); + rp correct(rp const & skp1_ext); /* compute correction to extrapolated filter state skp1_ext = {x(k+1|k), P(k+1|k)}, * for a single observation z(k+1, j) = .input.z()[j] */ - ref::rp correct1(ref::rp const & skp1_ext, + rp correct1(rp const & skp1_ext, uint32_t j); void display(std::ostream & os) const; @@ -111,7 +111,7 @@ namespace xo { /* system state: timestamp, estimated process state, process covariance * asof beginning of this step */ - ref::rp state_; + rp state_; /* input: observations at time t(k+1) */ KalmanFilterInputPtr input_; }; /*KalmanFilterStep*/ diff --git a/include/xo/kalmanfilter/KalmanFilterSvc.hpp b/include/xo/kalmanfilter/KalmanFilterSvc.hpp index 94702a52..4b63cbee 100644 --- a/include/xo/kalmanfilter/KalmanFilterSvc.hpp +++ b/include/xo/kalmanfilter/KalmanFilterSvc.hpp @@ -15,19 +15,19 @@ namespace xo { * sinks that want to consume KalmanFilterSvc events will use * .attach_sink() (or .add_callback()) */ - class KalmanFilterSvc : public xo::reactor::Sink1>, - public xo::reactor::DirectSourcePtr> { + class KalmanFilterSvc : public xo::reactor::Sink1>, + public xo::reactor::DirectSourcePtr> { public: using AbstractSource = xo::reactor::AbstractSource; public: /* named ctor idiom */ - static ref::rp make(KalmanFilterSpec spec); + static rp make(KalmanFilterSpec spec); KalmanFilter const & filter() const { return filter_; } /* notify incoming observations; will trigger kalman filter step */ - void notify_ev(ref::rp const & input_kp1) override; + void notify_ev(rp const & input_kp1) override; // ----- inherited from reactor::AbstractSink ----- @@ -52,7 +52,7 @@ namespace xo { /* passive kalman filter */ KalmanFilter filter_; /* receive filter input from this source; see .attach_input() */ - ref::rp input_src_; + rp input_src_; /* counts lifetime #of input events (see .notify_ev()) */ uint32_t n_in_ev_ = 0; /* publish filter state updates to these callbacks */ diff --git a/src/kalmanfilter/KalmanFilter.cpp b/src/kalmanfilter/KalmanFilter.cpp index 56507650..5c415580 100644 --- a/src/kalmanfilter/KalmanFilter.cpp +++ b/src/kalmanfilter/KalmanFilter.cpp @@ -24,7 +24,7 @@ namespace xo { {} /*ctor*/ void - KalmanFilter::notify_input(ref::rp const & input_kp1) + KalmanFilter::notify_input(rp const & input_kp1) { scope log(XO_ENTER0(info)); diff --git a/src/kalmanfilter/KalmanFilterEngine.cpp b/src/kalmanfilter/KalmanFilterEngine.cpp index e9533549..fe783e8a 100644 --- a/src/kalmanfilter/KalmanFilterEngine.cpp +++ b/src/kalmanfilter/KalmanFilterEngine.cpp @@ -19,9 +19,9 @@ namespace xo { namespace kalman { // ----- KalmanFilterEngine ----- - ref::rp + rp KalmanFilterEngine::extrapolate(utc_nanos tkp1, - ref::rp const & s, + rp const & s, KalmanFilterTransition const & f) { //constexpr char const * c_self_name @@ -60,7 +60,7 @@ namespace xo { } /*extrapolate*/ VectorXd - KalmanFilterEngine::kalman_gain1(ref::rp const & skp1_ext, + KalmanFilterEngine::kalman_gain1(rp const & skp1_ext, KalmanFilterObservable const & h, uint32_t j) { @@ -110,7 +110,7 @@ namespace xo { } /*kalman_gain1*/ MatrixXd - KalmanFilterEngine::kalman_gain(ref::rp const & skp1_ext, + KalmanFilterEngine::kalman_gain(rp const & skp1_ext, KalmanFilterObservable const & h) { scope log(XO_DEBUG(false /*debug_enabled*/)); @@ -214,10 +214,10 @@ namespace xo { return K; } /*kalman_gain*/ - ref::rp - KalmanFilterEngine::correct1(ref::rp const & skp1_ext, + rp + KalmanFilterEngine::correct1(rp const & skp1_ext, KalmanFilterObservable const & h, - ref::rp const & zkp1, + rp const & zkp1, uint32_t j) { uint32_t n = skp1_ext->n_state(); @@ -261,10 +261,10 @@ namespace xo { zkp1); } /*correct1*/ - ref::rp - KalmanFilterEngine::correct(ref::rp const & skp1_ext, + rp + KalmanFilterEngine::correct(rp const & skp1_ext, KalmanFilterObservable const & h, - ref::rp const & zkp1) + rp const & zkp1) { uint32_t n = skp1_ext->n_state(); /* K :: [n x m] */ @@ -300,23 +300,23 @@ namespace xo { zkp1); } /*correct*/ - ref::rp + rp KalmanFilterEngine::step(utc_nanos tkp1, - ref::rp const & sk, + rp const & sk, KalmanFilterTransition const & Fk, KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1) + rp const & zkp1) { - ref::rp skp1_ext + rp skp1_ext = KalmanFilterEngine::extrapolate(tkp1, sk, Fk); - ref::rp skp1 + rp skp1 = KalmanFilterEngine::correct(skp1_ext, Hkp1, zkp1); return skp1; } /*step*/ - ref::rp + rp KalmanFilterEngine::step(KalmanFilterStep const & step_spec) { return step(step_spec.tkp1(), @@ -326,24 +326,24 @@ namespace xo { step_spec.input()); } /*step*/ - ref::rp + rp KalmanFilterEngine::step1(utc_nanos tkp1, - ref::rp const & sk, + rp const & sk, KalmanFilterTransition const & Fk, KalmanFilterObservable const & Hkp1, - ref::rp const & zkp1, + rp const & zkp1, uint32_t j) { - ref::rp skp1_ext + rp skp1_ext = KalmanFilterEngine::extrapolate(tkp1, sk, Fk); - ref::rp skp1 + rp skp1 = KalmanFilterEngine::correct1(skp1_ext, Hkp1, zkp1, j); return skp1; } /*step1*/ - ref::rp + rp KalmanFilterEngine::step1(KalmanFilterStep const & step_spec, uint32_t j) { diff --git a/src/kalmanfilter/KalmanFilterInput.cpp b/src/kalmanfilter/KalmanFilterInput.cpp index 96f0d6ca..a93a855c 100644 --- a/src/kalmanfilter/KalmanFilterInput.cpp +++ b/src/kalmanfilter/KalmanFilterInput.cpp @@ -19,7 +19,7 @@ namespace xo { using std::uint32_t; namespace kalman { - ref::rp + rp KalmanFilterInput::make(utc_nanos tkp1, VectorXb const & presence, VectorXd const & z, @@ -28,7 +28,7 @@ namespace xo { return new KalmanFilterInput(tkp1, presence, z, Rd); } /*make*/ - ref::rp + rp KalmanFilterInput::make_present(utc_nanos tkp1, VectorXd const & z) { diff --git a/src/kalmanfilter/KalmanFilterInputToConsole.cpp b/src/kalmanfilter/KalmanFilterInputToConsole.cpp index 8fadf031..b3cd9d21 100644 --- a/src/kalmanfilter/KalmanFilterInputToConsole.cpp +++ b/src/kalmanfilter/KalmanFilterInputToConsole.cpp @@ -7,7 +7,7 @@ namespace xo { using xo::xtag; namespace kalman { - ref::rp + rp KalmanFilterInputToConsole::make() { return new KalmanFilterInputToConsole(); } /*make*/ diff --git a/src/kalmanfilter/KalmanFilterState.cpp b/src/kalmanfilter/KalmanFilterState.cpp index 4b5679e3..411e6a26 100644 --- a/src/kalmanfilter/KalmanFilterState.cpp +++ b/src/kalmanfilter/KalmanFilterState.cpp @@ -14,7 +14,7 @@ namespace xo { using xo::reflect::TaggedRcptr; using xo::reflect::StructReflector; using xo::time::utc_nanos; - using xo::ref::rp; + using xo::rp; using logutil::matrix; using logutil::vector; //using xo::scope; @@ -190,7 +190,7 @@ namespace xo { // ----- KalmanFilterStateExt ----- - ref::rp + rp KalmanFilterStateExt::initial(utc_nanos t0, VectorXd x0, MatrixXd P0) diff --git a/src/kalmanfilter/KalmanFilterStateToConsole.cpp b/src/kalmanfilter/KalmanFilterStateToConsole.cpp index 9559d339..c7043e2c 100644 --- a/src/kalmanfilter/KalmanFilterStateToConsole.cpp +++ b/src/kalmanfilter/KalmanFilterStateToConsole.cpp @@ -7,7 +7,7 @@ namespace xo { using xo::xtag; namespace kalman { - ref::rp + rp KalmanFilterStateToConsole::make() { return new KalmanFilterStateToConsole(); } /*make*/ diff --git a/src/kalmanfilter/KalmanFilterStep.cpp b/src/kalmanfilter/KalmanFilterStep.cpp index 8ca90211..7313924f 100644 --- a/src/kalmanfilter/KalmanFilterStep.cpp +++ b/src/kalmanfilter/KalmanFilterStep.cpp @@ -13,7 +13,7 @@ namespace xo { using Eigen::VectorXd; namespace kalman { - ref::rp + rp KalmanFilterStep::extrapolate() const { return KalmanFilterEngine::extrapolate(this->tkp1(), @@ -22,14 +22,14 @@ namespace xo { } /*extrapolate*/ MatrixXd - KalmanFilterStep::gain(ref::rp const & skp1_ext) const + KalmanFilterStep::gain(rp const & skp1_ext) const { return KalmanFilterEngine::kalman_gain(skp1_ext, this->obs()); } /*gain*/ VectorXd - KalmanFilterStep::gain1(ref::rp const & skp1_ext, + KalmanFilterStep::gain1(rp const & skp1_ext, uint32_t j) const { return KalmanFilterEngine::kalman_gain1(skp1_ext, @@ -38,16 +38,16 @@ namespace xo { } /*gain1*/ - ref::rp - KalmanFilterStep::correct(ref::rp const & skp1_ext) + rp + KalmanFilterStep::correct(rp const & skp1_ext) { return KalmanFilterEngine::correct(skp1_ext, this->obs(), this->input()); } /*correct*/ - ref::rp - KalmanFilterStep::correct1(ref::rp const & skp1_ext, + rp + KalmanFilterStep::correct1(rp const & skp1_ext, uint32_t j) { return KalmanFilterEngine::correct1(skp1_ext, diff --git a/src/kalmanfilter/KalmanFilterSvc.cpp b/src/kalmanfilter/KalmanFilterSvc.cpp index 5e32b102..c1392dfd 100644 --- a/src/kalmanfilter/KalmanFilterSvc.cpp +++ b/src/kalmanfilter/KalmanFilterSvc.cpp @@ -3,7 +3,7 @@ #include "KalmanFilterSvc.hpp" namespace xo { - using xo::ref::rp; + using xo::rp; using xo::scope; using xo::xtag; @@ -19,7 +19,7 @@ namespace xo { {} void - KalmanFilterSvc::notify_ev(ref::rp const & input_kp1) + KalmanFilterSvc::notify_ev(rp const & input_kp1) { this->filter_.notify_input(input_kp1); diff --git a/utest/KalmanFilter.test.cpp b/utest/KalmanFilter.test.cpp index fea572ec..a310adb4 100644 --- a/utest/KalmanFilter.test.cpp +++ b/utest/KalmanFilter.test.cpp @@ -26,7 +26,7 @@ namespace xo { using xo::time::timeutil; using xo::time::utc_nanos; using xo::time::seconds; - using xo::ref::rp; + using xo::rp; using xo::log_level; using logutil::matrix; using xo::print::ccs; @@ -144,7 +144,7 @@ namespace xo { z_stats.include_sample(z[0]); - ref::rp inputk + rp inputk = KalmanFilterInput::make_present(tkp1, z); KalmanFilterStep step_spec = spec.make_step(sk, inputk); @@ -259,7 +259,7 @@ namespace xo { z_stats.include_sample(z[0]); - ref::rp inputk + rp inputk = KalmanFilterInput::make_present(tkp1, z); KalmanFilterStep step_spec @@ -408,7 +408,7 @@ namespace xo { INFO(tostr(xtag("i_step", i_step), xtag("z", z))); - ref::rp inputk + rp inputk = KalmanFilterInput::make_present(tkp1, z); KalmanFilterStep step_spec = spec.make_step(sk, inputk); @@ -571,7 +571,7 @@ namespace xo { z_stats.include_sample(z[0]); - ref::rp inputk + rp inputk = KalmanFilterInput::make_present(tkp1, z); KalmanFilterStep step_spec = spec.make_step(sk, inputk); rp skp1 = KalmanFilterEngine::step(step_spec); From 94e59d7248848f5ab8c10f8630ddb0eff1e653da Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 19:24:15 -0500 Subject: [PATCH 1517/2524] xo-pykalmanfilter: build: update to latest xo-cmake macros --- CMakeLists.txt | 15 ++++++++++++++- cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d044769a..15d971db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,10 +4,23 @@ cmake_minimum_required(VERSION 3.10) project(xo_pykalmanfilter VERSION 1.0) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.cmake) -xo_cxx_toplevel_options() +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings + +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- add_subdirectory(src/pykalmanfilter) +# ---------------------------------------------------------------- +# provide find_package() support + xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From a9daaa3278d70626d66e252c4733cd58080f12d3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 14 Sep 2024 19:24:30 -0500 Subject: [PATCH 1518/2524] xo-pykalmanfilter: bugfix: track xo::ref::rp -> xo::rp --- src/pykalmanfilter/pykalmanfilter.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pykalmanfilter/pykalmanfilter.cpp b/src/pykalmanfilter/pykalmanfilter.cpp index 1f7320ce..4255335b 100644 --- a/src/pykalmanfilter/pykalmanfilter.cpp +++ b/src/pykalmanfilter/pykalmanfilter.cpp @@ -48,7 +48,6 @@ namespace xo { using xo::reactor::ReactorSource; using xo::reactor::PtrEventStore; using xo::reflect::SelfTagging; - using xo::ref::rp; using xo::time::utc_nanos; using Eigen::VectorXd; using Eigen::VectorXi; @@ -164,7 +163,7 @@ namespace xo { // ----- xo::kalman::KalmanFilterStep ----- py::class_(m, "KalmanFilterStep") - .def(py::init, KalmanFilterTransition, KalmanFilterObservable, ref::rp>(), + .def(py::init, KalmanFilterTransition, KalmanFilterObservable, rp>(), py::arg("state"), py::arg("model"), py::arg("obs"), py::arg("input")) .def_property_readonly("state", &KalmanFilterStep::state) .def_property_readonly("model", &KalmanFilterStepBase::model) From 563b7250d29873f08876eec8eec84ca66345e0cb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 15 Sep 2024 11:13:09 -0500 Subject: [PATCH 1519/2524] xo-pyexpression: build: drop xo-pyreflect dep --- src/pyexpression/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyexpression/CMakeLists.txt b/src/pyexpression/CMakeLists.txt index 6e81f021..0c9714bb 100644 --- a/src/pyexpression/CMakeLists.txt +++ b/src/pyexpression/CMakeLists.txt @@ -5,5 +5,5 @@ set(SELF_SRCS pyexpression.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} xo_expression) -xo_pybind11_dependency(${SELF_LIB} xo_pyreflect) +#xo_pybind11_dependency(${SELF_LIB} xo_pyreflect) xo_dependency(${SELF_LIB} refcnt) From 99b56a354087d7fdbdaf516392b48cde0af2be8f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 15 Sep 2024 11:14:20 -0500 Subject: [PATCH 1520/2524] xo-pyjit: drop xo-pyexpression dep --- src/pyjit/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyjit/CMakeLists.txt b/src/pyjit/CMakeLists.txt index d6d84521..31d04f60 100644 --- a/src/pyjit/CMakeLists.txt +++ b/src/pyjit/CMakeLists.txt @@ -5,5 +5,5 @@ set(SELF_SRCS pyjit.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} xo_jit) -xo_pybind11_dependency(${SELF_LIB} xo_pyexpression) +#xo_pybind11_dependency(${SELF_LIB} xo_pyexpression) xo_dependency(${SELF_LIB} refcnt) From 88852c1498e0c8daabf67c3e13df4760bae47ae3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 15 Sep 2024 11:14:36 -0500 Subject: [PATCH 1521/2524] xo-pyjit: jit.codegen() -> jit.codegen_toplevel() --- src/pyjit/pyjit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 184fb309..195a908d 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -150,7 +150,7 @@ namespace xo { py::doc("write to console with state of all jit-owned dynamic libraries")) .def("codegen", [](MachPipeline & jit, const rp & expr) { - return jit.codegen(expr.borrow()); + return jit.codegen_toplevel(expr.borrow()); }, py::arg("x"), py::doc("generate llvm (IR) code for Expression x"), From 0ed8a1f43561a23ac313b0906c32ac00aeaca10f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 15 Sep 2024 11:40:25 -0500 Subject: [PATCH 1522/2524] xo-websock: build: update to use latest xo-cmake macros --- CMakeLists.txt | 20 ++------------------ cmake/xo-bootstrap-macros.cmake | 33 +++++++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e3d66093..a3a89b7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,25 +3,11 @@ cmake_minimum_required(VERSION 3.10) project(websock VERSION 1.0) -enable_language(CXX) -# common XO cmake macros (see proj/xo-cmake) +include(GNUInstallDirs) include(cmake/xo-bootstrap-macros.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/*) +xo_cxx_toplevel_options3() # ---------------------------------------------------------------- # c++ settings @@ -31,8 +17,6 @@ set(PROJECT_CXX_FLAGS "") #set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") add_definitions(${PROJECT_CXX_FLAGS}) -xo_toplevel_compile_options() - # ---------------------------------------------------------------- add_subdirectory(src/websock) diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake index 96592216..aba31169 100644 --- a/cmake/xo-bootstrap-macros.cmake +++ b/cmake/xo-bootstrap-macros.cmake @@ -1,14 +1,35 @@ -if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL "prefix")) - # default to typical install location for xo-project-macros - set(CMAKE_MODULE_PATH ${CMAKE_INSTALL_PREFIX}/share/cmake) +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") endif() +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + if (NOT XO_SUBMODULE_BUILD) - message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") - message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() endif() # needs to have been installed somewhere on CMAKE_MODULE_PATH, # (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) # -include(xo_macros/xo-project-macros) +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() From c42b6c70eb5ab5ca539bb1e74a00e953d7071942 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 15 Sep 2024 11:41:13 -0500 Subject: [PATCH 1523/2524] xo-websock: bugfix: track xo::ref::rp -> xo::rp --- include/xo/websock/DynamicEndpoint.hpp | 2 +- include/xo/websock/Webserver.hpp | 4 ++-- include/xo/websock/WebsocketSink.hpp | 8 ++++---- src/websock/DynamicEndpoint.cpp | 1 - src/websock/Webserver.cpp | 1 - src/websock/WebsocketSink.cpp | 13 ++++++------- 6 files changed, 13 insertions(+), 16 deletions(-) diff --git a/include/xo/websock/DynamicEndpoint.hpp b/include/xo/websock/DynamicEndpoint.hpp index 8753b835..7403904f 100644 --- a/include/xo/websock/DynamicEndpoint.hpp +++ b/include/xo/websock/DynamicEndpoint.hpp @@ -67,7 +67,7 @@ namespace xo { * send output to ws_sink */ CallbackId subscribe(std::string const & incoming_uri, - ref::rp const & ws_sink) const; + rp const & ws_sink) const; /* unsubscribe stream from this endpoint; * reverses the effect of a previous call to .subscribe() diff --git a/include/xo/websock/Webserver.hpp b/include/xo/websock/Webserver.hpp index 57b0ccf7..12ad316f 100644 --- a/include/xo/websock/Webserver.hpp +++ b/include/xo/websock/Webserver.hpp @@ -83,8 +83,8 @@ namespace xo { * the underlying libwebsocket library is not advertised to be * threadsafe */ - static ref::rp make(WebserverConfig const & ws_config, - ref::rp const & pjson); + static rp make(WebserverConfig const & ws_config, + rp const & pjson); /* current state */ virtual Runstate state() const = 0; diff --git a/include/xo/websock/WebsocketSink.hpp b/include/xo/websock/WebsocketSink.hpp index 863bda34..cd7aa96a 100644 --- a/include/xo/websock/WebsocketSink.hpp +++ b/include/xo/websock/WebsocketSink.hpp @@ -17,10 +17,10 @@ namespace xo { using PrintJson = xo::json::PrintJson; public: - static ref::rp make(ref::rp const & websrv, - ref::rp const & pjson, - uint32_t session_id, - std::string const & stream_name); + static rp make(rp const & websrv, + rp const & pjson, + uint32_t session_id, + std::string const & stream_name); }; /*WebsocketSink*/ } /*namespace web*/ } /*namespace xo*/ diff --git a/src/websock/DynamicEndpoint.cpp b/src/websock/DynamicEndpoint.cpp index 3df46d47..5b7daea9 100644 --- a/src/websock/DynamicEndpoint.cpp +++ b/src/websock/DynamicEndpoint.cpp @@ -8,7 +8,6 @@ namespace xo { using xo::web::Alist; using xo::fn::CallbackId; - using xo::ref::rp; namespace web { DynamicEndpoint::DynamicEndpoint(std::string uri_pattern, diff --git a/src/websock/Webserver.cpp b/src/websock/Webserver.cpp index 09f90439..6e830425 100644 --- a/src/websock/Webserver.cpp +++ b/src/websock/Webserver.cpp @@ -34,7 +34,6 @@ namespace xo { using xo::reactor::AbstractSink; using xo::json::PrintJson; using xo::fn::CallbackId; - using xo::ref::rp; using xo::scope; using xo::xtag; diff --git a/src/websock/WebsocketSink.cpp b/src/websock/WebsocketSink.cpp index 1c406432..f0a9fef5 100644 --- a/src/websock/WebsocketSink.cpp +++ b/src/websock/WebsocketSink.cpp @@ -16,9 +16,8 @@ namespace xo { using xo::reflect::Reflect; using xo::reflect::TaggedPtr; using xo::reflect::TypeDescr; - using xo::ref::rp; using xo::ref::brw; - using xo::print::quoted; + using xo::print::quot; using xo::print::qcstr; using xo::scope; using xo::xtag; @@ -36,8 +35,8 @@ namespace xo { using AbstractSource = reactor::AbstractSource; public: - WebsocketSinkImpl(ref::rp const & websrv, - ref::rp const & pjson, + WebsocketSinkImpl(rp const & websrv, + rp const & pjson, uint32_t session_id, std::string stream_name) : websrv_{std::move(websrv)}, @@ -56,7 +55,7 @@ namespace xo { virtual TypeDescr sink_ev_type() const override; virtual bool allow_volatile_source() const override { return true; } virtual uint32_t n_in_ev() const override { return n_in_ev_; } - virtual void attach_source(ref::rp const & src) override; + virtual void attach_source(rp const & src) override; virtual void notify_ev_tp(TaggedPtr const & ev_tp) override; private: @@ -66,9 +65,9 @@ namespace xo { */ std::string name_; /* webserver implementation */ - ref::rp websrv_; + rp websrv_; /* print arbitrary reflected stuff as json */ - ref::rp pjson_; + rp pjson_; /* websocket session id# - events arriving at this sink * will be sent only to the session identified by .session_id */ From 4fb94bc2d68c6e215bf57e04d953825adca84eae Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 15 Sep 2024 12:56:36 -0500 Subject: [PATCH 1524/2524] xo-tokenizer: fix duplicate case statements --- include/xo/tokenizer/tokenizer.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/include/xo/tokenizer/tokenizer.hpp b/include/xo/tokenizer/tokenizer.hpp index d73e163f..8caa1440 100644 --- a/include/xo/tokenizer/tokenizer.hpp +++ b/include/xo/tokenizer/tokenizer.hpp @@ -157,7 +157,6 @@ namespace xo { case '*': /* not punctuation -- allowed in symbol */ return false; - case '*': case '/': /* not punctuation -- for symmetry with +,- */ return false; @@ -346,12 +345,20 @@ namespace xo { if (token_text.size() == 1) { /* standalone '*' */ tk_type = tokentype::tk_star; + ++ix; + } else { + /* '*' isn't punctuation -- but may allow appearance in a longer token + * + * thinking that x*y is a symbol with an embedded '*' character; + * in particular want to support kebab-case symbols like 'foo-config' + */ } break; case '/': if (token_text.size() == 1) { /* standalone '/' */ tk_type = tokentype::tk_slash; + ++ix; } break; case '"': @@ -530,13 +537,6 @@ namespace xo { tk_type = tokentype::tk_semicolon; ++ix; break; - case '*': - /* '*' isn't punctuation, since can appear within symbol. - * However it cannot begin a symbol.. - */ - tk_type = tokentype::tk_star; - ++ix; - break; case ':': { log && log("colon or assignment token"); From c393512912ac851a6416866cc79e4d34bd2425b1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 15 Sep 2024 13:37:03 -0500 Subject: [PATCH 1525/2524] xo-pyexpression: bugfix: xo::ref::rp -> xo::rp --- src/pyexpression/pyexpression.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index d5a3cf47..fe51f6fb 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -33,7 +33,6 @@ namespace xo { using xo::ast::IfExpr; using xo::ast::make_ifexpr; using xo::reflect::TaggedPtr; - using xo::ref::rp; namespace py = pybind11; PYBIND11_MODULE(XO_PYEXPRESSION_MODULE_NAME(), m) { From 07e5a1f349cfa46d8f5c230f2d585ae10e4ed93c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 15 Sep 2024 15:32:40 -0500 Subject: [PATCH 1526/2524] xo-jit: bugfix: track xo::ref::rp -> xo::rp --- include/xo/jit/activation_record.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index e3eb7a66..73c9e52e 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -114,9 +114,9 @@ namespace xo { using TypeDescr = xo::reflect::TypeDescr; public: - activation_record(const ref::rp & lm); + activation_record(const rp & lm); - const ref::rp lambda() const { return lambda_; } + const rp lambda() const { return lambda_; } /** retrieve @c llvm::Value* representing the primary stack location * for formal parameter @p var_name @@ -189,7 +189,7 @@ namespace xo { * runtime environment records. * **/ - ref::rp lambda_; + rp lambda_; /** @c binding_v_[i] specifies how/where we mean to navigate to * location for formal parameter number *i* of @ref lambda_. From f3441b583a678fb7cb476d0c7ed9bfe31716dfb4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 21 Sep 2024 12:19:48 -0400 Subject: [PATCH 1527/2524] xo-websock: bugfix: track quoted->quot --- src/websock/WebsocketSink.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/websock/WebsocketSink.cpp b/src/websock/WebsocketSink.cpp index 1c406432..07dded1b 100644 --- a/src/websock/WebsocketSink.cpp +++ b/src/websock/WebsocketSink.cpp @@ -18,7 +18,7 @@ namespace xo { using xo::reflect::TypeDescr; using xo::ref::rp; using xo::ref::brw; - using xo::print::quoted; + using xo::print::quot; using xo::print::qcstr; using xo::scope; using xo::xtag; @@ -104,7 +104,7 @@ namespace xo { std::stringstream ss; /* format message envelope */ - ss << "{" << qcstr("stream") << ": " << quoted(this->stream_name_) + ss << "{" << qcstr("stream") << ": " << quot(this->stream_name_) << ", " << qcstr("event") << ": "; /* format event as json */ From b361811b976568c0736827b323e1b154a4cdda3d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 2 May 2025 19:21:17 -0500 Subject: [PATCH 1528/2524] bugfix: compiler nit --- src/pyreflect/pyreflect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyreflect/pyreflect.cpp b/src/pyreflect/pyreflect.cpp index 503635ed..35b971a3 100644 --- a/src/pyreflect/pyreflect.cpp +++ b/src/pyreflect/pyreflect.cpp @@ -16,7 +16,7 @@ namespace xo { using xo::time::utc_nanos; using xo::ref::unowned_ptr; - using xo::ref::rp; + using xo::rp; namespace py = pybind11; namespace reflect { From c9cea234bc0c7405d07a3ae81e29ebb63248a87e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 2 May 2025 20:13:55 -0500 Subject: [PATCH 1529/2524] bugfix: Quantity -> xquantity --- src/pyunit/pyunit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyunit/pyunit.cpp b/src/pyunit/pyunit.cpp index 9fef80a3..7dd9992b 100644 --- a/src/pyunit/pyunit.cpp +++ b/src/pyunit/pyunit.cpp @@ -13,7 +13,7 @@ namespace xo { namespace py = pybind11; using Unit = xo::qty::natural_unit; - using XoQuantity = xo::qty::Quantity; + using XoQuantity = xo::qty::xquantity; namespace qty { PYBIND11_MODULE(PYUNIT_MODULE_NAME(), m) { From 38f5e58ed51f777cc09d480e0e1d608bb24f0f46 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 3 May 2025 09:18:34 -0700 Subject: [PATCH 1530/2524] nit: xo::ref::rp -> xo::rp --- src/pyexpression/pyexpression.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyexpression/pyexpression.cpp b/src/pyexpression/pyexpression.cpp index d5a3cf47..8536e34e 100644 --- a/src/pyexpression/pyexpression.cpp +++ b/src/pyexpression/pyexpression.cpp @@ -33,7 +33,7 @@ namespace xo { using xo::ast::IfExpr; using xo::ast::make_ifexpr; using xo::reflect::TaggedPtr; - using xo::ref::rp; + using xo::rp; namespace py = pybind11; PYBIND11_MODULE(XO_PYEXPRESSION_MODULE_NAME(), m) { From 9439f0f2210cdeb22dd14046e23d889253f7c554 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 7 May 2025 20:47:22 -0500 Subject: [PATCH 1531/2524] xo-jit: clang18/llvm18 compile fixes (temp patches) --- CMakeLists.txt | 5 ----- include/xo/jit/Jit.hpp | 2 +- src/jit/MachPipeline.cpp | 7 +++++++ src/jit/activation_record.cpp | 2 +- src/jit/type2llvm.cpp | 7 +++++-- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ee6351c..30e3f9cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,9 +31,4 @@ add_subdirectory(utest) # reminder: must come last: docs targets depend on all the other library/utest targets add_subdirectory(docs) -# ---------------------------------------------------------------- -# docs targets depend on all the other library/utest targets -# -#add_subdirectory(docs) - # end CMakeLists.txt diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index f983fb2e..5130b844 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -15,7 +15,7 @@ # include "llvm/ExecutionEngine/Orc/IRCompileLayer.h" # include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h" # include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" -# include "llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h" +# include "llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h" // need llvm18 # include "llvm/ExecutionEngine/SectionMemoryManager.h" # include "llvm/IR/DataLayout.h" # include "llvm/IR/LLVMContext.h" diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 850d0801..d45f03db 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -604,6 +604,8 @@ namespace xo { return ir_builder.CreateUDiv(args[1], args[2]); case llvmintrinsic::fp_add: return ir_builder.CreateFAdd(args[1], args[2]); + case llvmintrinsic::fp_sub: + return ir_builder.CreateFSub(args[1], args[2]); case llvmintrinsic::fp_mul: return ir_builder.CreateFMul(args[1], args[2]); case llvmintrinsic::fp_div: @@ -954,6 +956,11 @@ namespace xo { llvm::IRBuilder<> & ir_builder) { switch(expr->extype()) { + case exprtype::define: + case exprtype::assign: + case exprtype::sequence: + case exprtype::convert: + break; case exprtype::constant: return this->codegen_constant(ConstantInterface::from(expr)); case exprtype::primitive: diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index 7b1228e6..5d0fd3ad 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -10,7 +10,7 @@ namespace xo { using std::cerr; using std::endl; - activation_record::activation_record(const ref::rp & lm) + activation_record::activation_record(const rp & lm) : lambda_{lm}, binding_v_(lm->n_arg()) { diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index e5c07cac..8bfb2a3b 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -205,9 +205,12 @@ namespace xo { } /*struct_td_to_llvm_type*/ llvm::PointerType * - type2llvm::pointer_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr pointer_td) + type2llvm::pointer_td_to_llvm_type(xo::ref::brw llvm_cx + , TypeDescr pointer_td + ) { + (void)pointer_td; + assert(pointer_td->is_pointer()); #ifdef OBSOLETE From 38f2da8a8d6823e4292bf1b96e911b50b805bc95 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 May 2025 20:43:12 -0500 Subject: [PATCH 1532/2524] bugfix: looks like need distinct custom target names Care about this in submodule build w/ cmake 3.29.2 --- cmake/xo_macros/xo_cxx.cmake | 73 ++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 06e5e8c3..8ab14f94 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -15,41 +15,48 @@ endmacro() # deprecated -- prefer xo_cxx_toplevel_options2() macro(xo_cxx_toplevel_options) message(WARNING "deprecated: prefer xo_cxx_toplevel_options2") + + message("xo_cxx_toplevel_options: PROJECT=${PROJECT_NAME}") + enable_language(CXX) xo_toplevel_compile_options() xo_toplevel_testing_options() - add_custom_target(all_executables) + add_custom_target(all_executables_${PROJECT_NAME}) set_property( - TARGET all_executables + TARGET all_executables_${PROJECT_NAME} PROPERTY targets "") - add_custom_target(all_libraries) + add_custom_target(all_libraries_${PROJECT_NAME}) set_property( - TARGET all_libraries + TARGET all_libraries_${PROJECT_NAME} PROPERTY targets "") endmacro() # deprecated -- prefer xo_cxx_toplevel_options3() macro(xo_cxx_toplevel_options2) - enable_language(CXX) - xo_toplevel_compile_options() - enable_testing() + if (NOT DEFINED _xo_cxx_toplevel_done) + message("xo_cxx_toplevel_options2: PROJECT=${PROJECT_NAME}") - add_custom_target(all_executables) - set_property( - TARGET all_executables - PROPERTY targets "") + enable_language(CXX) + xo_toplevel_compile_options() + enable_testing() - add_custom_target(all_libraries) - set_property( - TARGET all_libraries - PROPERTY targets "") + add_custom_target(all_executables_${PROJECT_NAME}) + set_property( + TARGET all_executables_${PROJECT_NAME} + PROPERTY targets "") - add_custom_target(all_utest_executables) - set_property( - TARGET all_utest_executables - PROPERTY targets "") + add_custom_target(all_libraries_${PROJECT_NAME}) + set_property( + TARGET all_libraries_${PROJECT_NAME} + PROPERTY targets "") + + add_custom_target(all_utest_executables_${PROJECT_NAME}) + set_property( + TARGET all_utest_executables_${PROJECT_NAME} + PROPERTY targets "") + endif() endmacro() macro(xo_cxx_toplevel_options3) @@ -62,9 +69,9 @@ macro(xo_toplevel_testing_options) add_code_coverage() add_code_coverage_all_targets(EXCLUDE /nix/store* utest/*) - add_custom_target(all_utest_executables) + add_custom_target(all_utest_executables_${PROJECT_NAME}) set_property( - TARGET all_utest_executables + TARGET all_utest_executables_${PROJECT_NAME} PROPERTY targets "") endmacro() @@ -257,7 +264,7 @@ macro(xo_utest_coverage_config2) set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov) # collect utest deps (like xo_doxygen_collect_deps()) - get_target_property(_all_utests all_utest_executables targets) + get_target_property(_all_utests all_utest_executables_${PROJECT_NAME} targets) message(DEBUG "_all_utests=${_all_utests}") # 'test' target should always be out-of-date @@ -292,9 +299,9 @@ endmacro() # all add their target to ALL_LIBRARY_TARGETS. # macro(xo_doxygen_collect_deps) - get_target_property(_all_exes all_executables targets) - get_target_property(_all_libs all_libraries targets) - get_target_property(_all_utests all_utest_executables targets) + get_target_property(_all_exes all_executables_${PROJECT_NAME} targets) + get_target_property(_all_libs all_libraries_${PROJECT_NAME} targets) + get_target_property(_all_utests all_utest_executables_${PROJECT_NAME} targets) message(DEBUG "_all_exes=${_all_exes}") message(DEBUG "_all_libs=${_all_libs}") @@ -577,7 +584,7 @@ macro(xo_add_shared_library4 target projectTargets targetversion soversion sourc endforeach() set_property( - TARGET all_libraries + TARGET all_libraries_${PROJECT_NAME} APPEND PROPERTY targets ${target}) @@ -613,7 +620,7 @@ macro(xo_add_shared_library3 target projectTargets targetversion soversion sourc endforeach() set_property( - TARGET all_libraries + TARGET all_libraries_${PROJECT_NAME} APPEND PROPERTY targets ${target}) @@ -648,7 +655,7 @@ macro(xo_add_shared_library target targetversion soversion sources) endforeach() set_property( - TARGET all_libraries + TARGET all_libraries_${PROJECT_NAME} APPEND PROPERTY targets ${target}) @@ -672,7 +679,7 @@ macro(xo_add_headeronly_library4 target projectTargets) add_library(${target} INTERFACE) set_property( - TARGET all_libraries + TARGET all_libraries_${PROJECT_NAME} APPEND PROPERTY targets ${target}) @@ -695,7 +702,7 @@ macro(xo_add_headeronly_library target) add_library(${target} INTERFACE) set_property( - TARGET all_libraries + TARGET all_libraries_${PROJECT_NAME} APPEND PROPERTY targets ${target}) @@ -729,7 +736,7 @@ macro(xo_add_executable target sources) # such as xo_doxygen_collect_deps() # set_property( - TARGET all_executables + TARGET all_executables_${PROJECT_NAME} APPEND PROPERTY targets ${target}) endmacro() @@ -750,7 +757,7 @@ macro(xo_add_utest_executable target sources) endforeach() set_property( - TARGET all_utest_executables + TARGET all_utest_executables_${PROJECT_NAME} APPEND PROPERTY targets ${target}) @@ -1335,7 +1342,7 @@ macro(xo_pybind11_library target projectTargets source_files) pybind11_add_module(${target} MODULE ${source_files}) set_property( - TARGET all_libraries + TARGET all_libraries_${PROJECT_NAME} APPEND PROPERTY targets ${target}) From 89ef68f5f2c0dd09166b2a84a9b376cdbc24eb0c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 May 2025 23:38:48 -0500 Subject: [PATCH 1533/2524] + XO_UMBRELLA_REPO_SUBDIR --- cmake/xo_macros/xo_cxx.cmake | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cmake/xo_macros/xo_cxx.cmake b/cmake/xo_macros/xo_cxx.cmake index 8ab14f94..a5c0e3c3 100644 --- a/cmake/xo_macros/xo_cxx.cmake +++ b/cmake/xo_macros/xo_cxx.cmake @@ -1052,13 +1052,14 @@ macro(xo_dependency_helper target visibility dep) xo_establish_submodule_build() if(XO_SUBMODULE_BUILD) - if(EXISTS ${XO_UMBRELLA_SOURCE_DIR}/repo/xo-${_nxo_dep}) - xo_dependency_helper1(${target} ${visibility} repo/xo-${_nxo_dep}/include) + if(EXISTS ${XO_UMBRELLA_SOURCE_DIR}/${XO_UMBRELLA_REPO_SUBDIR}/xo-${_nxo_dep}) + xo_dependency_helper1(${target} ${visibility} ${XO_UMBRELLA_REPO_SUBDIR}/xo-${_nxo_dep}/include) endif() - if(EXISTS ${XO_UMBRELLA_SOURCE_DIR}/repo/${_nxo_dep}) - xo_dependency_helper1(${target} ${visibility} repo/${_nxo_dep}/include) + if(EXISTS ${XO_UMBRELLA_SOURCE_DIR}/${XO_UMBRELLA_REPO_SUBDIR}/${_nxo_dep}) + xo_dependency_helper1(${target} ${visibility} ${XO_UMBRELLA_REPO_SUBDIR}/${_nxo_dep}/include) endif() + else() message(STATUS "[${target}] find_package(${dep}) (xo_dependency_helper)") find_package(${dep} CONFIG REQUIRED) @@ -1401,6 +1402,10 @@ macro(xo_pybind11_dependency target dep) if(XO_SUBMODULE_BUILD) # ok to keep dep libraries on link line in submodule build #message("xo_pybind11_dependency: ${target}: don't clobber ${dep}.INTERFACE_LINK_LIBRARIES") + + # looks like also broken in submodule build? + #message("-- [${target}] remove ${dep}.INTERFACE_LINK_LIBRARIES to avoid problems with transitive deps (xo_pybind11_dependency)") + #set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") else() message("-- [${target}] remove ${dep}.INTERFACE_LINK_LIBRARIES to avoid problems with transitive deps (xo_pybind11_dependency)") set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") From bcd86e532442eb8e9379b59c23c89e879c4b24ed Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 May 2025 23:40:30 -0500 Subject: [PATCH 1534/2524] (clang 15) compiler nit --- include/xo/refcnt/Refcounted.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/xo/refcnt/Refcounted.hpp b/include/xo/refcnt/Refcounted.hpp index 41abf938..bab9f1ba 100644 --- a/include/xo/refcnt/Refcounted.hpp +++ b/include/xo/refcnt/Refcounted.hpp @@ -310,6 +310,11 @@ namespace xo { return *this; } + Borrow& operator=(const Borrow& x) { + ptr_ = x.get(); + return *this; + } + private: T * ptr_ = nullptr; }; /*Borrow*/ From 830c6ebe55b161afd735712d1e0cc8146d16e122 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 May 2025 23:46:28 -0500 Subject: [PATCH 1535/2524] xo-tokenizer: (clang 15) compiler nits --- include/xo/tokenizer/span.hpp | 2 +- include/xo/tokenizer/tokenizer.hpp | 2 ++ utest/tokenizer.test.cpp | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/include/xo/tokenizer/span.hpp b/include/xo/tokenizer/span.hpp index 241f52aa..22695ec5 100644 --- a/include/xo/tokenizer/span.hpp +++ b/include/xo/tokenizer/span.hpp @@ -71,7 +71,7 @@ namespace xo { /** @brief create span with first @p z members of this span removed **/ span after_prefix(size_type z) const { - if (z > hi_ - lo_) + if (lo_ + z > hi_) z = hi_ - lo_; return span(lo_ + z, hi_); diff --git a/include/xo/tokenizer/tokenizer.hpp b/include/xo/tokenizer/tokenizer.hpp index 8caa1440..ba340b1a 100644 --- a/include/xo/tokenizer/tokenizer.hpp +++ b/include/xo/tokenizer/tokenizer.hpp @@ -646,6 +646,8 @@ namespace xo { } else if (is_2char_punctuation(*ix)) { CharT ch1 = *ix; + (void)ch1; + ++ix; if (ix == input.hi()) { diff --git a/utest/tokenizer.test.cpp b/utest/tokenizer.test.cpp index e0ec6a56..44600e7f 100644 --- a/utest/tokenizer.test.cpp +++ b/utest/tokenizer.test.cpp @@ -111,6 +111,8 @@ namespace xo { {"let", false, token::let(), true}, {"in", false, token::in(), true}, {"end", false, token::end(), true}, + + {"*", false, token::star_token(), true}, }; } From 5d8cef515ec51bfd9dfa1df6d21d5d58987f51fa Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 May 2025 23:48:24 -0500 Subject: [PATCH 1536/2524] xo-unit: + indentlog dep --- cmake/xo_unitConfig.cmake.in | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/xo_unitConfig.cmake.in b/cmake/xo_unitConfig.cmake.in index 1d19e419..fb3c5def 100644 --- a/cmake/xo_unitConfig.cmake.in +++ b/cmake/xo_unitConfig.cmake.in @@ -3,6 +3,7 @@ include(CMakeFindDependencyMacro) find_dependency(xo_ratio) +find_dependency(xo_indentlog) #find_dependency(reflect) #find_dependency(subsys) #find_dependency(Eigen3) From 57bf06a68f79d533b18069041b7b287a568c1779 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 May 2025 23:50:16 -0500 Subject: [PATCH 1537/2524] xo-websock: compile fix (websock version?) --- src/websock/WebsockUtil.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/websock/WebsockUtil.cpp b/src/websock/WebsockUtil.cpp index 6cd6b19d..e3dc4b57 100644 --- a/src/websock/WebsockUtil.cpp +++ b/src/websock/WebsockUtil.cpp @@ -26,7 +26,9 @@ namespace xo { CASE(LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS); CASE(LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS); CASE(LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION); +#ifdef OBSOLETE // at least on osx w/ nixpkgs dd868b7bd4d1407d607da0d1d9c5eca89132e2f7 CASE(LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY); +#endif CASE(LWS_CALLBACK_SSL_INFO); CASE(LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION); CASE(LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED); From b531e382c29bfb961a9136cc3f3c8526e9063b22 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 May 2025 23:51:08 -0500 Subject: [PATCH 1538/2524] xo-unit: + indentlog dep --- cmake/xo_unitConfig.cmake.in | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cmake/xo_unitConfig.cmake.in b/cmake/xo_unitConfig.cmake.in index fb3c5def..871a9b44 100644 --- a/cmake/xo_unitConfig.cmake.in +++ b/cmake/xo_unitConfig.cmake.in @@ -3,11 +3,7 @@ include(CMakeFindDependencyMacro) find_dependency(xo_ratio) -find_dependency(xo_indentlog) -#find_dependency(reflect) -#find_dependency(subsys) -#find_dependency(Eigen3) -#find_dependency(webutil) +find_dependency(indentlog) #find_dependency(printjson) #find_dependency(callback) From 3ce41f42cadf92dfaee4f7152750385a0023e7a4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 May 2025 23:52:40 -0500 Subject: [PATCH 1539/2524] compile nit: refactor debris --- src/pywebsock/pywebsock.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pywebsock/pywebsock.cpp b/src/pywebsock/pywebsock.cpp index e92c43ee..bbaa19c1 100644 --- a/src/pywebsock/pywebsock.cpp +++ b/src/pywebsock/pywebsock.cpp @@ -13,7 +13,7 @@ namespace xo { using xo::web::Webserver; using xo::web::Runstate; using xo::json::PrintJsonSingleton; - using xo::ref::rp; + using xo::rp; namespace py = pybind11; namespace web { From 4d0e708ea4cea2853471f0e20b216680ef813430 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 May 2025 23:54:24 -0500 Subject: [PATCH 1540/2524] xo-pyunit: fix missing cmake deps --- cmake/xo_pyunitConfig.cmake.in | 7 +++++++ src/pyunit/CMakeLists.txt | 3 +++ 2 files changed, 10 insertions(+) diff --git a/cmake/xo_pyunitConfig.cmake.in b/cmake/xo_pyunitConfig.cmake.in index 9c15f36a..18187c96 100644 --- a/cmake/xo_pyunitConfig.cmake.in +++ b/cmake/xo_pyunitConfig.cmake.in @@ -1,4 +1,11 @@ @PACKAGE_INIT@ +include(CMakeFindDependencyMacro) + +find_dependency(xo_pyutil) +find_dependency(xo_unit) +find_dependency(refcnt) +find_dependency(indentlog) + include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") check_required_components("@PROJECT_NAME@") diff --git a/src/pyunit/CMakeLists.txt b/src/pyunit/CMakeLists.txt index 9ef0a302..393a1387 100644 --- a/src/pyunit/CMakeLists.txt +++ b/src/pyunit/CMakeLists.txt @@ -6,6 +6,9 @@ set(SELF_SRCS pyunit.cpp) # ---------------------------------------------------------------- xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) +xo_pybind11_dependency(${SELF_LIB} xo_pyutil) xo_pybind11_dependency(${SELF_LIB} xo_unit) +xo_pybind11_dependency(${SELF_LIB} refcnt) +xo_pybind11_dependency(${SELF_LIB} indentlog) # CMakeLists.txt From f94005141181b8458cf1b1b2979a3a2cda04a1df Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 May 2025 23:55:33 -0500 Subject: [PATCH 1541/2524] xo-pysimulator: cmake py2py dep fixes --- src/pysimulator/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pysimulator/CMakeLists.txt b/src/pysimulator/CMakeLists.txt index 541c3378..08616537 100644 --- a/src/pysimulator/CMakeLists.txt +++ b/src/pysimulator/CMakeLists.txt @@ -4,6 +4,6 @@ set(SELF_LIB xo_pysimulator) set(SELF_SRCS pysimulator.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) -xo_pybind11_dependency(${SELF_LIB} xo_pyreactor) +xo_pybind11_header_dependency(${SELF_LIB} xo_pyreactor) xo_pybind11_dependency(${SELF_LIB} simulator) -xo_pybind11_dependency(${SELF_LIB} xo_pyutil) +xo_pybind11_header_dependency(${SELF_LIB} xo_pyutil) From ee20e799063f8924150525328d81df6c29065707 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 May 2025 23:58:16 -0500 Subject: [PATCH 1542/2524] xo-pyjit: build + dep fixes --- src/pyjit/CMakeLists.txt | 2 +- src/pyjit/pyjit.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyjit/CMakeLists.txt b/src/pyjit/CMakeLists.txt index 31d04f60..b9c1a2b5 100644 --- a/src/pyjit/CMakeLists.txt +++ b/src/pyjit/CMakeLists.txt @@ -5,5 +5,5 @@ set(SELF_SRCS pyjit.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} xo_jit) -#xo_pybind11_dependency(${SELF_LIB} xo_pyexpression) +xo_pybind11_header_dependency(${SELF_LIB} xo_pyexpression) xo_dependency(${SELF_LIB} refcnt) diff --git a/src/pyjit/pyjit.cpp b/src/pyjit/pyjit.cpp index 527cfe1a..c83ef36a 100644 --- a/src/pyjit/pyjit.cpp +++ b/src/pyjit/pyjit.cpp @@ -18,7 +18,7 @@ namespace xo { using xo::pyutil::pycaller_base; using xo::pyutil::pycaller; using xo::reflect::Reflect; - using xo::ref::rp; + using xo::rp; //using xo::ref::Refcount; using xo::ref::unowned_ptr; namespace py = pybind11; From 7fdf5b4a78d2e74ef554d35106ccf456cb51e50d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 May 2025 23:58:58 -0500 Subject: [PATCH 1543/2524] xo-pyexpression: cmake dep fixes --- src/pyexpression/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyexpression/CMakeLists.txt b/src/pyexpression/CMakeLists.txt index 0c9714bb..1f49b0b9 100644 --- a/src/pyexpression/CMakeLists.txt +++ b/src/pyexpression/CMakeLists.txt @@ -5,5 +5,6 @@ set(SELF_SRCS pyexpression.cpp) xo_pybind11_library(${SELF_LIB} ${PROJECT_NAME}Targets ${SELF_SRCS}) xo_pybind11_dependency(${SELF_LIB} xo_expression) -#xo_pybind11_dependency(${SELF_LIB} xo_pyreflect) +# always use this when xo_pyfoo depends on xo_pybar +xo_pybind11_header_dependency(${SELF_LIB} xo_pyreflect) xo_dependency(${SELF_LIB} refcnt) From 855887df71015791fb87af513c8ce3b3ecd06472 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 May 2025 00:00:26 -0500 Subject: [PATCH 1544/2524] xo-jit: compiler nit: (clang 15) --- include/xo/jit/activation_record.new.hpp | 66 + include/xo/jit/activation_record.orig.hpp | 41 + include/xo/jit/type2llvm.hpp | 3 + src/jit/MachPipeline.new.cpp | 1341 +++++++++++++++++++++ src/jit/MachPipeline.orig.cpp | 1064 ++++++++++++++++ src/jit/activation_record.new.cpp | 47 + src/jit/activation_record.orig.cpp | 45 + 7 files changed, 2607 insertions(+) create mode 100644 include/xo/jit/activation_record.new.hpp create mode 100644 include/xo/jit/activation_record.orig.hpp create mode 100644 src/jit/MachPipeline.new.cpp create mode 100644 src/jit/MachPipeline.orig.cpp create mode 100644 src/jit/activation_record.new.cpp create mode 100644 src/jit/activation_record.orig.cpp diff --git a/include/xo/jit/activation_record.new.hpp b/include/xo/jit/activation_record.new.hpp new file mode 100644 index 00000000..7c70ba3f --- /dev/null +++ b/include/xo/jit/activation_record.new.hpp @@ -0,0 +1,66 @@ +/** @file activation_record.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "LlvmContext.hpp" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +# include +# include +#pragma GCC diagnostic pop +#include +//#include + +namespace xo { + namespace jit { + /** scope for a stack frame associated with a user-defined function + * + * each function needs its own IR builder, to keep track of things like insert point + **/ + class activation_record { + public: + activation_record(llvm::Function * llvm_fn, + llvm::AllocaInst * frame) : frame_{frame} { + int i_arg = 0; + for (auto & arg : llvm_fn->args()) { + std::string arg_name = std::string(arg.getName()); + + name2ix_map_[arg_name] = 2 + i_arg; + } + } + + std::int32_t lookup_var(const std::string & var_name) const; + +#ifdef OBSOLETE + llvm::AllocaInst * lookup_var(const std::string & var_name) const; + + llvm::AllocaInst * alloc_var(const std::string & var_name, + llvm::AllocaInst * alloca); +#endif + + private: + /** stack frame for a user-defined function (lambda) **/ + llvm::AllocaInst * frame_ = nullptr; + + /** for each formal parameter, + * reports its position in stack frame. + * This is the position to use with getelementptr, + * i.e. +2 to skip first two slots, that are reserved + * for nextframe pointer (slot 0) + unwind pointer (slot 1) + **/ + std::map name2ix_map_; + +#ifdef OBSOLETE + /** maps named slots in a stack frame to logical addresses **/ + std::map frame_; /* <-> kaleidoscope NamedValues */ +#endif + }; /*activation_record*/ + + } /*namespace jit*/ +} /*namespace xo*/ + + +/** end activation_record.hpp **/ diff --git a/include/xo/jit/activation_record.orig.hpp b/include/xo/jit/activation_record.orig.hpp new file mode 100644 index 00000000..c2aba2dd --- /dev/null +++ b/include/xo/jit/activation_record.orig.hpp @@ -0,0 +1,41 @@ +/** @file activation_record.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "LlvmContext.hpp" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +# include +# include +#pragma GCC diagnostic pop +#include +//#include + +namespace xo { + namespace jit { + /** scope for a stack frame associated with a user-defined function + * + * each function needs its own IR builder, to keep track of things like insert point + **/ + class activation_record { + public: + activation_record() = default; + + llvm::AllocaInst * lookup_var(const std::string & var_name) const; + + llvm::AllocaInst * alloc_var(const std::string & var_name, + llvm::AllocaInst * alloca); + + private: + /** maps named slots in a stack frame to logical addresses **/ + std::map frame_; /* <-> kaleidoscope NamedValues */ + }; /*activation_record*/ + + } /*namespace jit*/ +} /*namespace xo*/ + + +/** end activation_record.hpp **/ diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index 35a6e739..fb108a3e 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -8,7 +8,10 @@ #include "LlvmContext.hpp" #include "xo/expression/Lambda.hpp" #include "xo/reflect/TypeDescr.hpp" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" #include +#pragma GCC diagnostic pop //#include namespace xo { diff --git a/src/jit/MachPipeline.new.cpp b/src/jit/MachPipeline.new.cpp new file mode 100644 index 00000000..c4faff4a --- /dev/null +++ b/src/jit/MachPipeline.new.cpp @@ -0,0 +1,1341 @@ +/* @file MachPipeline.cpp */ + +#include "MachPipeline.hpp" +#include + +namespace xo { + using xo::ast::exprtype; + using xo::ast::Expression; + using xo::ast::ConstantInterface; + //using xo::ast::FunctionInterface; + using xo::ast::PrimitiveInterface; + using xo::ast::Lambda; + using xo::ast::Variable; + using xo::ast::Apply; + using xo::ast::IfExpr; + using xo::ast::llvmintrinsic; + using xo::reflect::Reflect; + using xo::reflect::StructMember; + using xo::reflect::TypeDescr; + using llvm::orc::ExecutionSession; + using llvm::DataLayout; + using std::cerr; + using std::endl; + + namespace jit { + void + MachPipeline::init_once() { + static bool s_init_once = false; + + if (!s_init_once) { + s_init_once = true; + + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); + } + } /*init_once*/ + + /* tracking KaleidoscopeJIT::Create() here.. + * + * Verified: + * + 'execution session' as per Kaleidoscope JIT, + * can instantiate from python + * + 'jit object layer' + * (realtime dynamic library object linking layer) + * + 'jit_compile_layer' + * + 'jit_our_dynamic_lib' + */ + llvm::Expected> + MachPipeline::make_aux() + { + MachPipeline::init_once(); + + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr jit = llvm_exit_on_err(Jit::Create()); + + return std::unique_ptr(new MachPipeline(std::move(jit) + )); + } /*make*/ + + xo::ref::rp + MachPipeline::make() { + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr jit = llvm_exit_on_err(make_aux()); + + return jit.release(); + } /*make*/ + + MachPipeline::MachPipeline(std::unique_ptr jit) + : jit_{std::move(jit)} + { + this->recreate_llvm_ir_pipeline(); + } + + void + MachPipeline::recreate_llvm_ir_pipeline() + { + //llvm_cx_ = std::make_unique(); + llvm_cx_ = LlvmContext::make(); + llvm_toplevel_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); + + llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); + llvm_module_->setDataLayout(this->jit_->data_layout()); + + if (!llvm_cx_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); + } + if (!llvm_toplevel_ir_builder_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm IR builder"); + } + if (!llvm_module_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm module"); + } + + ir_pipeline_ = new IrPipeline(llvm_cx_); + } /*recreate_llvm_ir_pipeline*/ + + const DataLayout & + MachPipeline::data_layout() const { + return this->jit_->data_layout(); + } + + const ExecutionSession * + MachPipeline::xsession() const { + return this->jit_->xsession(); + } + + /** identifies target host/architecture for machine code. + * e.g. "x86_64-unknown-linux-gnu" + **/ + const std::string & + MachPipeline::target_triple() const { + // although this getter is defined, seems to be empty in practice + //return llvm_module_->getTargetTriple(); + + return this->jit_->target_triple(); + } + + std::vector + MachPipeline::get_function_name_v() { + std::vector retval; + for (const auto & fn_name : *llvm_module_) + retval.push_back(fn_name.getName().str()); + + return retval; + } /*get_function_names*/ + + void + MachPipeline::dump_execution_session() { + this->jit_->dump_execution_session(); + } + + llvm::Value * + MachPipeline::codegen_constant(ref::brw expr) + { + TypeDescr td = expr->value_td(); + + if (Reflect::is_native(td)) { + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); + } + + return nullptr; + } /*codegen_constant*/ + + namespace { + /** REMINDER: + * 1. creation of llvm types is idempotent + * (duplicate calls will receive the same llvm::Type* pointer) + * 2. llvm::Types are never deleted. + **/ + + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td); + + /** obtain llvm representation for a function type with the same signature as + * that represented by @p fn_td + **/ + llvm::FunctionType * + function_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + int n_fn_arg = fn_td->n_fn_arg(); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(n_fn_arg); + + /** check function args are all known **/ + for (int i = 0; i < n_fn_arg; ++i) { + TypeDescr arg_td = fn_td->fn_arg(i); + + llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx, arg_td); + + if (!llvm_argtype) + return nullptr; + + llvm_argtype_v.push_back(llvm_argtype); + } + + TypeDescr retval_td = fn_td->fn_retval(); + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx, retval_td); + + if (!llvm_retval) + return nullptr; + + auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, + llvm_argtype_v, + false /*!varargs*/); + return llvm_fn_type; + } + + llvm::PointerType * + function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); + + /** like C: llvm IR doesn't support function-valued variables; + * it does however support pointer-to-function-valued variables + **/ + auto * llvm_ptr_type + = llvm::PointerType::get(llvm_fn_type, + 0 /*numbered address space*/); + + return llvm_ptr_type; + } + + /** + * Generate llvm::Type correspoinding to a TypeDescr for a struct. + **/ + llvm::StructType * + struct_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr struct_td) + { + // see + // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] + + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + /* note: object pointer ignored for struct types, + * since number of members is known at compile time + */ + int n_member = struct_td->n_child(nullptr /*&object*/); + + /* one type for each struct member */ + std::vector llvm_membertype_v; + llvm_membertype_v.reserve(n_member); + + for (int i = 0; i < n_member; ++i) { + StructMember const & sm = struct_td->struct_member(i); + + llvm_membertype_v.push_back(td_to_llvm_type(llvm_cx, + sm.get_member_td())); + } + + std::string struct_name = std::string(struct_td->short_name()); + + /* structs with names: within an llvmcontext, must be unique + * + * We can however compare the offsets recorded in xo::reflect with + * offsets chosen by llvm, *once we've created the llvm type* + * + * Also, we can't guarantee that a c++ type was completely reflected -- + * it's possible one or more members were omitted, in which case + * it's unlikely at best that llvm chooses the same layout. + * + * Instead: tell llvm to make packed struct, + * and introduce dummy members for padding. + * + * A consequence is we have to maintain mapping between llvm's + * member numbering and xo::reflect's + */ + llvm::StructType * llvm_struct_type + = llvm::StructType::create(llvm_cx_ref, + llvm_membertype_v, + llvm::StringRef(struct_name), + false /*!isPacked*/); + + /* TODO: inspect (how) offsets that llvm is using + * we need them to match what C++ chose + * + * (because we want jitted llvm code to interoperate with + * C++ library code that has structs) + */ + + // GetElementPtrInst is interesting, + // but I think that's for generating code + + return llvm_struct_type; + } /*struct_td_to_llvm_type*/ + + llvm::PointerType * + pointer_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr pointer_td) + { + assert(pointer_td->is_pointer()); + + TypeDescr dest_td = pointer_td->fixed_child_td(0); + + llvm::Type * llvm_dest_type = td_to_llvm_type(llvm_cx, dest_td); + + llvm::PointerType * llvm_ptr_type + = llvm::PointerType::getUnqual(llvm_dest_type); + + return llvm_ptr_type; + } /*pointer_td_llvm_type*/ + + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + if (td->is_function()) { + /* in this context, we're looking for a representation for a value, + * i.e. something that can be stored in a variable + */ + return function_td_to_llvm_fnptr_type(llvm_cx, td); + } else if (td->is_struct()) { + return struct_td_to_llvm_type(llvm_cx, td); + } else if (td->is_pointer()) { + return pointer_td_to_llvm_type(llvm_cx, td); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt1Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt8Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt16Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt32Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt64Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getFloatTy(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getDoubleTy(llvm_cx_ref); + } else { + cerr << "td_to_llvm_type: no llvm type available for T" + << xtag("T", td->short_name()) + << endl; + return nullptr; + } + } + } + + llvm::Type * + MachPipeline::codegen_type(TypeDescr td) { + return td_to_llvm_type(llvm_cx_.borrow(), td); + } + + llvm::Function * + MachPipeline::codegen_primitive(ref::brw expr) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + /** note: documentation (such as it is) for llvm::Function here: + * + * https://llvm.org/doxygenL/classllvm_1_1Function.html + **/ + + auto * fn = llvm_module_->getFunction(expr->name()); + + if (fn) { + /** function with this name already known to llvm module; + * use that definition + * + * TODO: verify that signatures match! + **/ + return fn; + } + + /** establish prototype for this function **/ + + TypeDescr fn_td = expr->valuetype(); + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + + if (!llvm_fn_type) + return nullptr; + + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + expr->name(), + llvm_module_.get()); + +#ifdef NOT_USING + // set names for arguments (for diagnostics?). Monkey-see-kaleidoscope-monkey-do here + { + int i_arg = 0; + for (auto & arg : fn->args()) { + std::stringstream ss; + ss << "x_" << i_arg; + + arg.setName(ss.str()); + ++i_arg; + } + } +#endif + + if (expr->explicit_symbol_def()) { + static llvm::ExitOnError llvm_exit_on_err; + + auto name = expr->name(); + auto fn_addr = expr->function_address(); + + log && log(xtag("sym", name), + xtag("mangled_sym", this->jit_->mangle(name))); + + llvm_exit_on_err(this->jit_->intern_symbol(name, fn_addr)); + +#ifdef NOT_USING + if (!llvm_result) { + cerr << "MachPipeline::codegen_primitive" + << ": intern_symbol failed" + << xtag("name", expr->name()) + << xtag("addr", expr->function_address()) + << endl; + + return nullptr; + } +#endif + } else { + log && log("not requiring absolute address", xtag("sym", expr->name())); + } + +#ifdef OBSOLETE + log && log("returning llvm function"); +#endif + + return fn; + } /*codegen_primitive*/ + + llvm::Value * + MachPipeline::codegen_apply(ref::brw apply, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("apply", apply)); + + // see here: + // https://stackoverflow.com/questions/54905211/how-to-implement-function-pointer-by-using-llvm-c-api + + using std::cerr; + using std::endl; + + /* IR for value in function position. + * Although it will generate a function (or pointer-to-function), + * it need not have inherited type llvm::Function. + */ + llvm::Value * llvm_fnval = nullptr; + llvmintrinsic intrinsic = llvmintrinsic::invalid; + /* function type in apply node's function position */ + TypeDescr ast_fn_td = apply->fn()->valuetype(); + { + /* special treatement for primitive in apply position: + * allows substituting LLVM intrinsic + */ + if (apply->fn()->extype() == exprtype::primitive) { + auto pm = PrimitiveInterface::from(apply->fn()); + + if (pm) { + llvm_fnval = this->codegen_primitive(pm); + /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ + intrinsic = pm->intrinsic(); + } + } else { + llvm_fnval = this->codegen(apply->fn(), ir_builder); + + /* we don't need any special checking here. + * already know (from xo-level checking) that pointer has the right type. + * + * Specifically, xo::ast::Apply::make() requires the expression in function position + * have suitable function type. + * + * Now: we have an llvm::Value (fn_value) representing the pointer. + * However it's not an llvm::Function instance, and we can't get one. + * + * (Older LLVM versions allowed getting the element type from a pointer, + * for some reasons that's deprecated at least in 18.1.5) + */ + } + } + + if (!llvm_fnval) { + return nullptr; + } + +#ifdef NOT_USING_DEBUG + cerr << "MachPipeline::codegen_apply: fn:" << endl; + fn->print(llvm::errs()); + cerr << endl; +#endif + + /* checks here will be redundant */ + +#ifdef REDUNDANT_TYPECHECK + if (apply->argv().size() != ast_fn_td->n_fn_arg()) { + cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" + //<< xtag("f", ast_fn->name()) + << xtag("n1", ast_fn_td->n_fn_arg()) + << xtag("n2", apply->argv().size()) + << endl; + + return nullptr; + } + + /** also check argument types **/ + for (size_t i = 0, n = ast_fn_td->n_fn_arg(); i < n; ++i) { + if (apply->argv()[i]->valuetype() != ast_fn_td->fn_arg(i)) { + cerr << "MachPipeline::codegen_apply: error: callee F for arg# I seeeing U instead of expected T" + << xtag("F", apply->fn()) + << xtag("I", i) + << xtag("U", apply->argv()[i]->valuetype()->short_name()) + << xtag("T", ast_fn_td->fn_arg(i)->short_name()) + << endl; + + return nullptr; + } + } +#endif + + std::vector args; + args.reserve(apply->argv().size()); + + int i = 0; + for (const auto & arg_expr : apply->argv()) { + auto * arg = this->codegen(arg_expr, ir_builder); + + if (log) { + /* TODO: print helper for llvm::Value* */ + std::string llvm_value_str; + llvm::raw_string_ostream ss(llvm_value_str); + arg->print(ss); + + log(xtag("i_arg", i), + xtag("arg", llvm_value_str)); + } + + args.push_back(arg); + ++i; + } + + /* if we have an intrinsic hint, + * then instead of invoking a function, + * we use some native machine instruction instead. + */ + switch(intrinsic) { + case llvmintrinsic::i_neg: + return ir_builder.CreateNeg(args[0]); + case llvmintrinsic::i_add: + return ir_builder.CreateAdd(args[0], args[1]); + case llvmintrinsic::i_sub: + return ir_builder.CreateSub(args[0], args[1]); + case llvmintrinsic::i_mul: + return ir_builder.CreateMul(args[0], args[1]); + case llvmintrinsic::i_sdiv: + return ir_builder.CreateSDiv(args[0], args[1]); + case llvmintrinsic::i_udiv: + return ir_builder.CreateUDiv(args[0], args[1]); + case llvmintrinsic::fp_add: + return ir_builder.CreateFAdd(args[0], args[1]); + case llvmintrinsic::fp_mul: + return ir_builder.CreateFMul(args[0], args[1]); + case llvmintrinsic::fp_div: + return ir_builder.CreateFDiv(args[0], args[1]); + case llvmintrinsic::invalid: + case llvmintrinsic::fp_sqrt: + case llvmintrinsic::fp_pow: + case llvmintrinsic::fp_sin: + case llvmintrinsic::fp_cos: + case llvmintrinsic::fp_tan: + case llvmintrinsic::n_intrinsic: /* n_intrinsic: not reachable */ + break; + } + + /* At least as of 18.1.5, LLVM needs us to supply function type + * when making a function call. In particular it doesn't remember + * the function type with each function pointer + */ + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); + + return ir_builder.CreateCall(llvm_fn_type, + llvm_fnval, + args, + "calltmp"); + + } /*codegen_apply*/ + + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + llvm::AllocaInst * + MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, + const std::string & var_name, + TypeDescr var_type) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn), + xtag("var_name", var_name), + xtag("var_type", var_type->short_name())); + + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), + llvm_fn->getEntryBlock().begin()); + + llvm::Type * llvm_var_type = td_to_llvm_type(llvm_cx_.borrow(), + var_type); + + log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); + if (log) { + std::string llvm_var_type_str; + llvm::raw_string_ostream ss(llvm_var_type_str); + llvm_var_type->print(ss); + + log(xtag("llvm_var_type", llvm_var_type_str)); + } + + if (!llvm_var_type) + return nullptr; + + llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(llvm_var_type, + nullptr, + var_name); + log && log(xtag("alloca", (void*)retval), + xtag("align", retval->getAlign().value()), + xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); + + return retval; + } /*create_entry_block_alloca*/ + + + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + llvm::AllocaInst * + MachPipeline::create_entry_frame_alloca(llvm::Function * llvm_fn, + llvm::StructType * frame_llvm_type) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn)); + + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), + llvm_fn->getEntryBlock().begin()); + + if (!frame_llvm_type) + return nullptr; + + if (log) { + std::string llvm_frame_type_str; + llvm::raw_string_ostream ss(llvm_frame_type_str); + frame_llvm_type->print(ss); + + log(xtag("frame_llvm_type", llvm_frame_type_str)); + } + + llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(frame_llvm_type, + nullptr, + llvm_fn->getName()); + + log && log(xtag("alloca", (void*)retval), + xtag("align", retval->getAlign().value()), + xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); + + return retval; + } /*create_entry_frame_alloca*/ + + std::vector> + MachPipeline::find_lambdas(ref::brw expr) const + { + std::vector> retval_v; + + expr->visit_preorder( + [&retval_v](ref::brw x) + { + if (x->extype() == exprtype::lambda) { + retval_v.push_back(Lambda::from(x)); + } + }); + + return retval_v; + } /*find_lambdas*/ + + llvm::Function * + MachPipeline::codegen_lambda_decl(ref::brw lambda) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * fn = llvm_module_->getFunction(lambda->name()); + + if (fn) { + return fn; + } + + /* establish prototype for this function */ + +#ifdef OBSOLETE + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_retval()); + + std::vector arg_type_v(lambda->n_arg()); + + for (size_t i = 0, n = lambda->n_arg(); i < n; ++i) { + arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_arg(i)); + } +#endif + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), + lambda->valuetype()); + + /* create (initially empty) function */ + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + lambda->name(), + llvm_module_.get()); + /* also capture argument names */ + { + int i = 0; + for (auto & arg : fn->args()) { + log && log("llvm formal param names", + xtag("i", i), + xtag("param", lambda->argv().at(i))); + + arg.setName(lambda->argv().at(i)->name()); + ++i; + } + } + + return fn; + } /*codegen_lambda_decl*/ + + namespace { + /** A function type: + * + * _baseframe* (*) (_baseframe* + **/ + llvm::FunctionType * + require_baseframe_unwind_llvm_type(xo::ref::brw llvm_cx, + llvm::PointerType * frameptr_llvm_type) + { + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(2); + + /* 1st arg is frame pointer */ + llvm_argtype_v.push_back(frameptr_llvm_type); + + /* 2nd arg is an i32. + * 0 -> unwind. + * 1 -> lift to heap (someday) + */ + llvm_argtype_v.push_back + (llvm::Type::getInt32Ty(llvm_cx->llvm_cx_ref())); + + /* return value is frame pointer */ + llvm::Type * retval_llvm_type = frameptr_llvm_type; + + auto * unwind_llvm_type = llvm::FunctionType::get(retval_llvm_type, + llvm_argtype_v, + false /*!varargs*/); + + return unwind_llvm_type; + } /*require_baseframe_unwind_llvm_type*/ + + /** Each lambda gets its own stack frame definition. + * However all the various frame representations share the same 'baseframe' + * prefix. + * + * _baseframe: + * ^ + * | + * +-------+ | + * next_frame [0] | o-------/ + * +-------| + * unwind_fn [1] | o-------> frame* (*)(frame*, ctl) + * +-------+ + * + * This helper function generates an llvm::Type* for a baseframe. + * It only needs to be invoked once (per LlvmContext, I guess ..) + **/ + llvm::StructType * + require_baseframe_llvm_type(xo::ref::brw llvm_cx) + { + /* _baseframe: base type for a stack frame */ + llvm::StructType * frame_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref(), + "_baseframe"); + + /* _baseframe*: pointer to a stack frame */ + llvm::PointerType * frameptr_llvm_type + = llvm::PointerType::getUnqual(frame_llvm_type); + + /* unwind function = frame[1] */ + llvm::FunctionType * unwind_llvm_type + = require_baseframe_unwind_llvm_type(llvm_cx, + frameptr_llvm_type); + + /* _baseframe members */ + std::vector llvm_membertype_v; + { + llvm_membertype_v.reserve(2); + + /* frame[0] = pointer to next frame */ + llvm_membertype_v.push_back(frameptr_llvm_type); + /* frame[1] = unwind function */ + llvm_membertype_v.push_back(unwind_llvm_type); + } + + frame_llvm_type->setBody(frameptr_llvm_type /*frame[0]*/, + unwind_llvm_type /*frame[1]*/); + + return frame_llvm_type; + } /*require_baseframe_llvm_type*/ + + llvm::PointerType * + require_baseframe_ptr_llvm_type(xo::ref::brw llvm_cx) + { + llvm::StructType * baseframe_llvm_type + = require_baseframe_llvm_type(llvm_cx); + + return llvm::PointerType::getUnqual(baseframe_llvm_type); + } + + /** need a supporting type for stack frame + * - so we can handle variables with non-trivial dtors + * (e.g. smart pointers) + * - so we can implement nested lexical scoping + * - so we can walk stack for exception handling + * - eventually: so we can eventually implement trampoline + * + * frame representation: + * + * ^ + * | + * +-------+ | + * next_frame [0] | o-------/ + * +-------| + * unwind_fn [1] | o-------> baseframe* (*)(baseframe*, ctl) + * +-------| + * arg[i] [2+i] | ... | + * +-------+ + * + * invoke frame.unwind_fn to dispose of a frame + * - ctl=0 dtor. deal with smart pointers etc. + * - ctl=1 copy. lift frame into heap for lambda capture + * + * every frame is a subtype of _baseframe (ofc llvm doesn't know this). + * See baseframe_llvm_type(), baseframe_unwind_llvm_type() above + * + * editor bait: activation_record_to_llvm_type + **/ + llvm::StructType * + frame_to_llvm_type(xo::ref::brw llvm_cx, + ref::brw lambda, + llvm::Function * lambda_llvm_fn) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + /* frame type doesn't need a name */ + llvm::StructType * frame_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref()); + + /* _baseframe */ + llvm::StructType * baseframe_llvm_type + = require_baseframe_llvm_type(llvm_cx); + + /* _baseframe*: pointer to a generic stack frame */ + llvm::PointerType * baseframeptr_llvm_type + = llvm::PointerType::getUnqual(baseframe_llvm_type); + + /* _baseframe* (*)(_baseframe*, i32) */ + llvm::FunctionType * unwind_llvm_type + = require_baseframe_unwind_llvm_type(llvm_cx, + baseframeptr_llvm_type); + + /* llvm_argtype_v: + * - llvm_argtype_v[0] = llvm::Type* for pointer to next_frame + * - llvm_argtype_v[1] = llvm::Type* for unwind_fn + * - llvm_argtype_v[2+i] = llvm::Type* for lambda->fn_arg(i) + */ + std::vector llvm_argtype_v; + { + llvm_argtype_v.reserve(2 + lambda_llvm_fn->arg_size()); + + /* frame pointer */ + llvm_argtype_v.push_back(baseframeptr_llvm_type); + /* unwind function */ + llvm_argtype_v.push_back(unwind_llvm_type); + + int i_arg = 0; + for (auto & arg : lambda_llvm_fn->args()) { + log && log(xtag("i_arg", i_arg), + xtag("param", std::string(arg.getName()))); + + llvm_argtype_v.push_back(td_to_llvm_type(llvm_cx, + lambda->fn_arg(i_arg))); + + ++i_arg; + } + } + + frame_llvm_type->setBody(llvm_argtype_v); + + return frame_llvm_type; + } /*frame_to_llvm_type*/ + } /*namespace*/ + + llvm::Function * + MachPipeline::codegen_lambda_defn(ref::brw lambda, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * llvm_fn = llvm_module_->getFunction(lambda->name()); + + if (!llvm_fn) { + /** function with this name not declared? **/ + cerr << "MachPipeline::codegen_lambda: function f not declared" + << xtag("f", lambda->name()) + << endl; + + return nullptr; + } + + /* generate function body -- code to execute later if/when function is called */ + + auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); + + ir_builder.SetInsertPoint(block); + + /* create stack frame */ + llvm::StructType * frame_llvm_type + = frame_to_llvm_type(llvm_cx_, + lambda, + llvm_fn); + + llvm::AllocaInst * frame_alloca + = create_entry_frame_alloca(llvm_fn, + frame_llvm_type); + + if (!frame_alloca) + return nullptr; + + /** Actual parameters will need their own activation record. + * Track its shape here. + * + * Local variables will be formal parameters to a nested lambda; + **/ + this->env_stack_.push(activation_record(llvm_fn, frame_alloca)); + + { + log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); + + int i_slot = 0; + int n_slot = 2 + lambda->n_arg(); + + auto arg_ix = llvm_fn->arg_begin(); + + for (i_slot = 0; i_slot < n_slot; ++i_slot) { + // TODO: move the frame-slot computation into helper function + + /* argument i_slot-2 */ + llvm::Value * frame_slot_ptr = nullptr; + { + /* note: we have to create instructions here because + * the llvm IR is invariant w.r.t. data layout. + * But we can't compute byte offsets until later + * when data layout is revealed. + */ + + llvm::Value * i32_zero + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32, 0)); + llvm::Value * i32_slot + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32, i_slot)); + + std::array index_v = { + {i32_zero /*deref frame pointer*/, + i32_slot /*field# relative to frame pointer*/}}; + + /* location in stack frame of arg #i */ + frame_slot_ptr + = ir_builder.CreateInBoundsGEP(frame_llvm_type, + frame_alloca, + index_v); + } + + if (i_slot == 0) { + /* frame pointer */ + assert(false); + } else if (i_slot == 1) { + /* unwind function */ + assert(false); + } else { + int i_arg = i_slot - 2; + + std::string arg_name = std::string(lambda->argv().at(i_arg)->name()); + + /* store param on function entry + * see codegen_variable() for corresponding load + */ + ir_builder.CreateStore(&(*arg_ix), + frame_slot_ptr /*destination*/); + + ++arg_ix; + } + } + } + + llvm::Value * retval = this->codegen(lambda->body(), ir_builder); + + if (retval) { + /* completes the function.. */ + ir_builder.CreateRet(retval); + + /* validate! always validate! */ + llvm::verifyFunction(*llvm_fn); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + llvm_fn->print(ss); + + log(xtag("IR-before-opt", buf)); + } + + /* optimize! improves IR */ + ir_pipeline_->run_pipeline(*llvm_fn); // llvm_fpmgr_->run(*llvm_fn, *llvm_famgr_); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + llvm_fn->print(ss); + + log(xtag("IR-after-opt", buf)); + } + } else { + /* oops, something went wrong */ + llvm_fn->eraseFromParent(); + + llvm_fn = nullptr; + } + + this->env_stack_.pop(); + + log && log("after pop, env stack size Z", xtag("Z", env_stack_.size())); + + return llvm_fn; + } /*codegen_lambda_defn*/ + + /* frame pointer */ + llvm::Value * + MachPipeline::codegen_global_frameptr(llvm::IRBuilder<> & ir_builder) + { + /* only want to jit this once */ + + /* though really: this would be once per thread */ + + llvm::Type * baseframe_ptr_llvm_type + = require_baseframe_ptr_llvm_type(llvm_cx_); + + /** nullptr - initial value for global frame pointer **/ + llvm::Constant * nullptr_llvm_value + = llvm::ConstantPointerNull::get(baseframe_ptr_llvm_type); + + llvm::Value * fp_llvm_value + = llvm::GlobalVariable(baseframe_ptr_llvm_type, + false /*!isConstant*/ + llvm::GlobalValues::LinkOnceOdrLinkage, + nullptr_llvm_value /*Initializer*/, + "frame_pointer"); + + } /*codegen_global_frameptr*/ + + llvm::Value * + MachPipeline::codegen_variable(ref::brw var, + llvm::IRBuilder<> & ir_builder) + { + if (env_stack_.empty()) { + cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" + << xtag("x", var->name()) + << endl; + + return nullptr; + } + + std::int32_t i_slot = env_stack_.top().lookup_var(var->name()); + //llvm::AllocaInst * alloca = env_stack_.top().lookup_var(var->name()); + + if (!alloca) + return nullptr; + + /* code to load value from stack */ + return ir_builder.CreateLoad(alloca->getAllocatedType(), + alloca, + var->name().c_str()); + } /*codegen_variable*/ + + llvm::Value * + MachPipeline::codegen_ifexpr(ref::brw expr, llvm::IRBuilder<> & ir_builder) + { + llvm::Value * test_ir = this->codegen(expr->test(), ir_builder); + + /** need test result in a variable **/ + llvm::Value * test_with_cmp_ir + = ir_builder.CreateFCmpONE(test_ir, + llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(0.0)), + "iftest"); + + llvm::Function * parent_fn = ir_builder.GetInsertBlock()->getParent(); + + /* when_true_bb, when_false_bb, merge_bb: + * initially-empty basic-blocks for {when_true, when_false, merged} codegen + */ + + /* when_true branch inserted at (current) end of function */ + llvm::BasicBlock * when_true_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_true", + parent_fn); + llvm::BasicBlock * when_false_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_false"); + + llvm::BasicBlock * merge_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "merge"); + + /* IR to direct control flow to one of {when_true_bb, when_false_bb}, + * depending on result of test_with_cmp_ir + */ + ir_builder.CreateCondBr(test_with_cmp_ir, + when_true_bb, + when_false_bb); + + /* populate when_true_bb */ + ir_builder.SetInsertPoint(when_true_bb); + + llvm::Value * when_true_ir = this->codegen(expr->when_true(), + ir_builder); + + if (!when_true_ir) + return nullptr; + + /* at end of when-true sequence, jump to merge suffix */ + ir_builder.CreateBr(merge_bb); + /* note: codegen for expr->when_true() may have altered builder's "current block" */ + when_true_bb = ir_builder.GetInsertBlock(); + + /* populate when_false_bb */ + parent_fn->insert(parent_fn->end(), when_false_bb); + ir_builder.SetInsertPoint(when_false_bb); + + llvm::Value * when_false_ir = this->codegen(expr->when_false(), ir_builder); + if (!when_false_ir) + return nullptr; + + /* at end of when-false sequence, jump to merge suffix */ + ir_builder.CreateBr(merge_bb); + /* note: codegen for expr->when_false() may have altered builder's "current block" */ + when_false_bb = ir_builder.GetInsertBlock(); + + /* merged suffix sequence */ + parent_fn->insert(parent_fn->end(), merge_bb); + ir_builder.SetInsertPoint(merge_bb); + + /** TODO: switch to getInt1Ty here **/ + llvm::PHINode * phi_node + = ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), + 2 /*#of branches being merged (?)*/, + "iftmp"); + phi_node->addIncoming(when_true_ir, when_true_bb); + phi_node->addIncoming(when_false_ir, when_false_bb); + + return phi_node; + } /*codegen_ifexpr*/ + + llvm::Value * + MachPipeline::codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder) + { + switch(expr->extype()) { + case exprtype::constant: + return this->codegen_constant(ConstantInterface::from(expr)); + case exprtype::primitive: + return this->codegen_primitive(PrimitiveInterface::from(expr)); + case exprtype::apply: + return this->codegen_apply(Apply::from(expr), ir_builder); + case exprtype::lambda: + return this->codegen_lambda_decl(Lambda::from(expr)); + case exprtype::variable: + return this->codegen_variable(Variable::from(expr), ir_builder); + case exprtype::ifexpr: + return this->codegen_ifexpr(IfExpr::from(expr), ir_builder); + case exprtype::invalid: + case exprtype::n_expr: + return nullptr; + break; + } + + cerr << "MachPipeline::codegen: error: no handler for expression of type T" + << xtag("T", expr->extype()) + << endl; + + return nullptr; + } /*codegen*/ + + llvm::Value * + MachPipeline::codegen_toplevel(ref::brw expr) + { + /* - Pass 1. + * get set of lambdas. + * Generate decls for all. + * + * TODO: for lexical scoping (not implemented yet) + * will need traversal that maintains stack + * of ancestor lambdas, or at least their + * activation records. May want to generalize + * activation_record so we can track the set of variables + * before generating AllocaInst's. + * + * - Pass 2. + * Generate code for lambdas. + * + * - Pass 3. + * If toplevel expressions isn't a lambda + * (? won't make sense at present when called from python) + * generate code for it too + */ + + /* Pass 1. */ + auto fn_v = this->find_lambdas(expr); + + for (auto lambda : fn_v) { + this->codegen_lambda_decl(lambda); + } + + /* Pass 2 */ + for (auto lambda : fn_v) { + this->codegen_lambda_defn(lambda, + *(this->llvm_toplevel_ir_builder_.get())); + } + + /* Pass 3 */ + if (expr->extype() == exprtype::lambda) { + /* code already generated in pass 2; + * look it up + */ + + return llvm_module_->getFunction(Lambda::from(expr)->name()); + } else { + /* toplevel expression isn't a lambda, + * so code for it hasn't been generated. + * Do that now + */ + return this->codegen(expr, + *(this->llvm_toplevel_ir_builder_.get())); + } + } /*codegen_toplevel*/ + + void + MachPipeline::dump_current_module() + { + /* dump module contents to console */ + + llvm_module_->dump(); + } + + void + MachPipeline::machgen_current_module() + { + static llvm::ExitOnError llvm_exit_on_err; + + auto tracker = this->jit_->dest_dynamic_lib_ref().createResourceTracker(); + + /* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create + * + * Note that @ref ir_pipeline_ holds reference, which is invalidated here + */ + auto ts_module = llvm::orc::ThreadSafeModule(std::move(llvm_module_), + std::move(llvm_cx_->llvm_cx())); + + /* note does not discard llvm_cx_->llvm_cx(), it's already been moved */ + this->llvm_cx_ = nullptr; + + llvm_exit_on_err(this->jit_->add_llvm_module(std::move(ts_module), tracker)); + + this->recreate_llvm_ir_pipeline(); + } /*machgen_current_module*/ + + std::string_view + MachPipeline::mangle(const std::string & sym) const + { + return this->jit_->mangle(sym); + } /*mangle*/ + + llvm::Expected + MachPipeline::lookup_symbol(const std::string & sym) + { + /* llvm_sym: ExecutorSymbolDef */ + auto llvm_sym_expected = this->jit_->lookup(sym); + + if (llvm_sym_expected) { + auto llvm_addr = llvm_sym_expected.get().getAddress(); + + return llvm_addr; + } else { + return llvm_sym_expected.takeError(); + } + } /*lookup_symbol*/ + + void + MachPipeline::display(std::ostream & os) const { + os << ""; + } + + std::string + MachPipeline::display_string() const { + return tostr(*this); + } + } /*namespace jit*/ +} /*namespace xo*/ + +/* end MachPipeline.cpp */ diff --git a/src/jit/MachPipeline.orig.cpp b/src/jit/MachPipeline.orig.cpp new file mode 100644 index 00000000..69cf0d02 --- /dev/null +++ b/src/jit/MachPipeline.orig.cpp @@ -0,0 +1,1064 @@ +/* @file MachPipeline.cpp */ + +#include "MachPipeline.hpp" +#include + +namespace xo { + using xo::ast::exprtype; + using xo::ast::Expression; + using xo::ast::ConstantInterface; + //using xo::ast::FunctionInterface; + using xo::ast::PrimitiveInterface; + using xo::ast::Lambda; + using xo::ast::Variable; + using xo::ast::Apply; + using xo::ast::IfExpr; + using xo::ast::llvmintrinsic; + using xo::reflect::Reflect; + using xo::reflect::StructMember; + using xo::reflect::TypeDescr; + using llvm::orc::ExecutionSession; + using llvm::DataLayout; + using std::cerr; + using std::endl; + + namespace jit { + void + MachPipeline::init_once() { + static bool s_init_once = false; + + if (!s_init_once) { + s_init_once = true; + + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); + } + } /*init_once*/ + + /* tracking KaleidoscopeJIT::Create() here.. + * + * Verified: + * + 'execution session' as per Kaleidoscope JIT, + * can instantiate from python + * + 'jit object layer' + * (realtime dynamic library object linking layer) + * + 'jit_compile_layer' + * + 'jit_our_dynamic_lib' + */ + llvm::Expected> + MachPipeline::make_aux() + { + MachPipeline::init_once(); + + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr jit = llvm_exit_on_err(Jit::Create()); + + return std::unique_ptr(new MachPipeline(std::move(jit) + )); + } /*make*/ + + xo::ref::rp + MachPipeline::make() { + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr jit = llvm_exit_on_err(make_aux()); + + return jit.release(); + } /*make*/ + + MachPipeline::MachPipeline(std::unique_ptr jit) + : jit_{std::move(jit)} + { + this->recreate_llvm_ir_pipeline(); + } + + void + MachPipeline::recreate_llvm_ir_pipeline() + { + //llvm_cx_ = std::make_unique(); + llvm_cx_ = LlvmContext::make(); + llvm_toplevel_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); + + llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); + llvm_module_->setDataLayout(this->jit_->data_layout()); + + if (!llvm_cx_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); + } + if (!llvm_toplevel_ir_builder_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm IR builder"); + } + if (!llvm_module_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm module"); + } + + ir_pipeline_ = new IrPipeline(llvm_cx_); + } /*recreate_llvm_ir_pipeline*/ + + const DataLayout & + MachPipeline::data_layout() const { + return this->jit_->data_layout(); + } + + const ExecutionSession * + MachPipeline::xsession() const { + return this->jit_->xsession(); + } + + /** identifies target host/architecture for machine code. + * e.g. "x86_64-unknown-linux-gnu" + **/ + const std::string & + MachPipeline::target_triple() const { + // although this getter is defined, seems to be empty in practice + //return llvm_module_->getTargetTriple(); + + return this->jit_->target_triple(); + } + + std::vector + MachPipeline::get_function_name_v() { + std::vector retval; + for (const auto & fn_name : *llvm_module_) + retval.push_back(fn_name.getName().str()); + + return retval; + } /*get_function_names*/ + + void + MachPipeline::dump_execution_session() { + this->jit_->dump_execution_session(); + } + + llvm::Value * + MachPipeline::codegen_constant(ref::brw expr) + { + TypeDescr td = expr->value_td(); + + if (Reflect::is_native(td)) { + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); + } + + return nullptr; + } /*codegen_constant*/ + + namespace { + /** REMINDER: + * 1. creation of llvm types is idempotent + * (duplicate calls will receive the same llvm::Type* pointer) + * 2. llvm::Types are never deleted. + **/ + + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td); + + /** obtain llvm representation for a function type with the same signature as + * that represented by @p fn_td + **/ + llvm::FunctionType * + function_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + int n_fn_arg = fn_td->n_fn_arg(); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(n_fn_arg); + + /** check function args are all known **/ + for (int i = 0; i < n_fn_arg; ++i) { + TypeDescr arg_td = fn_td->fn_arg(i); + + llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx, arg_td); + + if (!llvm_argtype) + return nullptr; + + llvm_argtype_v.push_back(llvm_argtype); + } + + TypeDescr retval_td = fn_td->fn_retval(); + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx, retval_td); + + if (!llvm_retval) + return nullptr; + + auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, + llvm_argtype_v, + false /*!varargs*/); + return llvm_fn_type; + } + + llvm::PointerType * + function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); + + /** like C: llvm IR doesn't support function-valued variables; + * it does however support pointer-to-function-valued variables + **/ + auto * llvm_ptr_type + = llvm::PointerType::get(llvm_fn_type, + 0 /*numbered address space*/); + + return llvm_ptr_type; + } + + /** + * Generate llvm::Type correspoinding to a TypeDescr for a struct. + **/ + llvm::StructType * + struct_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr struct_td) + { + // see + // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] + + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + /* note: object pointer ignored for struct types, + * since number of members is known at compile time + */ + int n_member = struct_td->n_child(nullptr /*&object*/); + + /* one type for each struct member */ + std::vector llvm_membertype_v; + llvm_membertype_v.reserve(n_member); + + for (int i = 0; i < n_member; ++i) { + StructMember const & sm = struct_td->struct_member(i); + + llvm_membertype_v.push_back(td_to_llvm_type(llvm_cx, + sm.get_member_td())); + } + + std::string struct_name = std::string(struct_td->short_name()); + + /* structs with names: within an llvmcontext, must be unique + * + * We can however compare the offsets recorded in xo::reflect with + * offsets chosen by llvm, *once we've created the llvm type* + * + * Also, we can't guarantee that a c++ type was completely reflected -- + * it's possible one or more members were omitted, in which case + * it's unlikely at best that llvm chooses the same layout. + * + * Instead: tell llvm to make packed struct, + * and introduce dummy members for padding. + * + * A consequence is we have to maintain mapping between llvm's + * member numbering and xo::reflect's + */ + llvm::StructType * llvm_struct_type + = llvm::StructType::create(llvm_cx_ref, + llvm_membertype_v, + llvm::StringRef(struct_name), + false /*!isPacked*/); + + /* TODO: inspect (how) offsets that llvm is using + * we need them to match what C++ chose + * + * (because we want jitted llvm code to interoperate with + * C++ library code that has structs) + */ + + // GetElementPtrInst is interesting, + // but I think that's for generating code + + return llvm_struct_type; + } /*struct_td_to_llvm_type*/ + + llvm::PointerType * + pointer_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr pointer_td) + { + assert(pointer_td->is_pointer()); + + TypeDescr dest_td = pointer_td->fixed_child_td(0); + + llvm::Type * llvm_dest_type = td_to_llvm_type(llvm_cx, dest_td); + + llvm::PointerType * llvm_ptr_type + = llvm::PointerType::getUnqual(llvm_dest_type); + + return llvm_ptr_type; + } /*pointer_td_llvm_type*/ + + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + if (td->is_function()) { + /* in this context, we're looking for a representation for a value, + * i.e. something that can be stored in a variable + */ + return function_td_to_llvm_fnptr_type(llvm_cx, td); + } else if (td->is_struct()) { + return struct_td_to_llvm_type(llvm_cx, td); + } else if (td->is_pointer()) { + return pointer_td_to_llvm_type(llvm_cx, td); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt1Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt8Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt16Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt32Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt64Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getFloatTy(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getDoubleTy(llvm_cx_ref); + } else { + cerr << "td_to_llvm_type: no llvm type available for T" + << xtag("T", td->short_name()) + << endl; + return nullptr; + } + } + } + + llvm::Type * + MachPipeline::codegen_type(TypeDescr td) { + return td_to_llvm_type(llvm_cx_.borrow(), td); + } + + llvm::Function * + MachPipeline::codegen_primitive(ref::brw expr) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + /** note: documentation (such as it is) for llvm::Function here: + * + * https://llvm.org/doxygenL/classllvm_1_1Function.html + **/ + + auto * fn = llvm_module_->getFunction(expr->name()); + + if (fn) { + /** function with this name already known to llvm module; + * use that definition + * + * TODO: verify that signatures match! + **/ + return fn; + } + + /** establish prototype for this function **/ + + TypeDescr fn_td = expr->valuetype(); + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + + if (!llvm_fn_type) + return nullptr; + + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + expr->name(), + llvm_module_.get()); + +#ifdef NOT_USING + // set names for arguments (for diagnostics?). Monkey-see-kaleidoscope-monkey-do here + { + int i_arg = 0; + for (auto & arg : fn->args()) { + std::stringstream ss; + ss << "x_" << i_arg; + + arg.setName(ss.str()); + ++i_arg; + } + } +#endif + + if (expr->explicit_symbol_def()) { + static llvm::ExitOnError llvm_exit_on_err; + + auto name = expr->name(); + auto fn_addr = expr->function_address(); + + log && log(xtag("sym", name), + xtag("mangled_sym", this->jit_->mangle(name))); + + llvm_exit_on_err(this->jit_->intern_symbol(name, fn_addr)); + +#ifdef NOT_USING + if (!llvm_result) { + cerr << "MachPipeline::codegen_primitive" + << ": intern_symbol failed" + << xtag("name", expr->name()) + << xtag("addr", expr->function_address()) + << endl; + + return nullptr; + } +#endif + } else { + log && log("not requiring absolute address", xtag("sym", expr->name())); + } + +#ifdef OBSOLETE + log && log("returning llvm function"); +#endif + + return fn; + } /*codegen_primitive*/ + + llvm::Value * + MachPipeline::codegen_apply(ref::brw apply, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("apply", apply)); + + // see here: + // https://stackoverflow.com/questions/54905211/how-to-implement-function-pointer-by-using-llvm-c-api + + using std::cerr; + using std::endl; + + /* IR for value in function position. + * Although it will generate a function (or pointer-to-function), + * it need not have inherited type llvm::Function. + */ + llvm::Value * llvm_fnval = nullptr; + llvmintrinsic intrinsic = llvmintrinsic::invalid; + /* function type in apply node's function position */ + TypeDescr ast_fn_td = apply->fn()->valuetype(); + { + /* special treatement for primitive in apply position: + * allows substituting LLVM intrinsic + */ + if (apply->fn()->extype() == exprtype::primitive) { + auto pm = PrimitiveInterface::from(apply->fn()); + + if (pm) { + llvm_fnval = this->codegen_primitive(pm); + /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ + intrinsic = pm->intrinsic(); + } + } else { + llvm_fnval = this->codegen(apply->fn(), ir_builder); + + /* we don't need any special checking here. + * already know (from xo-level checking) that pointer has the right type. + * + * Specifically, xo::ast::Apply::make() requires the expression in function position + * have suitable function type. + * + * Now: we have an llvm::Value (fn_value) representing the pointer. + * However it's not an llvm::Function instance, and we can't get one. + * + * (Older LLVM versions allowed getting the element type from a pointer, + * for some reasons that's deprecated at least in 18.1.5) + */ + } + } + + if (!llvm_fnval) { + return nullptr; + } + +#ifdef NOT_USING_DEBUG + cerr << "MachPipeline::codegen_apply: fn:" << endl; + fn->print(llvm::errs()); + cerr << endl; +#endif + + /* checks here will be redundant */ + +#ifdef REDUNDANT_TYPECHECK + if (apply->argv().size() != ast_fn_td->n_fn_arg()) { + cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" + //<< xtag("f", ast_fn->name()) + << xtag("n1", ast_fn_td->n_fn_arg()) + << xtag("n2", apply->argv().size()) + << endl; + + return nullptr; + } + + /** also check argument types **/ + for (size_t i = 0, n = ast_fn_td->n_fn_arg(); i < n; ++i) { + if (apply->argv()[i]->valuetype() != ast_fn_td->fn_arg(i)) { + cerr << "MachPipeline::codegen_apply: error: callee F for arg# I seeeing U instead of expected T" + << xtag("F", apply->fn()) + << xtag("I", i) + << xtag("U", apply->argv()[i]->valuetype()->short_name()) + << xtag("T", ast_fn_td->fn_arg(i)->short_name()) + << endl; + + return nullptr; + } + } +#endif + + std::vector args; + args.reserve(apply->argv().size()); + + int i = 0; + for (const auto & arg_expr : apply->argv()) { + auto * arg = this->codegen(arg_expr, ir_builder); + + if (log) { + /* TODO: print helper for llvm::Value* */ + std::string llvm_value_str; + llvm::raw_string_ostream ss(llvm_value_str); + arg->print(ss); + + log(xtag("i_arg", i), + xtag("arg", llvm_value_str)); + } + + args.push_back(arg); + ++i; + } + + /* if we have an intrinsic hint, + * then instead of invoking a function, + * we use some native machine instruction instead. + */ + switch(intrinsic) { + case llvmintrinsic::i_neg: + return ir_builder.CreateNeg(args[0]); + case llvmintrinsic::i_add: + return ir_builder.CreateAdd(args[0], args[1]); + case llvmintrinsic::i_sub: + return ir_builder.CreateSub(args[0], args[1]); + case llvmintrinsic::i_mul: + return ir_builder.CreateMul(args[0], args[1]); + case llvmintrinsic::i_sdiv: + return ir_builder.CreateSDiv(args[0], args[1]); + case llvmintrinsic::i_udiv: + return ir_builder.CreateUDiv(args[0], args[1]); + case llvmintrinsic::fp_add: + return ir_builder.CreateFAdd(args[0], args[1]); + case llvmintrinsic::fp_mul: + return ir_builder.CreateFMul(args[0], args[1]); + case llvmintrinsic::fp_div: + return ir_builder.CreateFDiv(args[0], args[1]); + case llvmintrinsic::invalid: + case llvmintrinsic::fp_sqrt: + case llvmintrinsic::fp_pow: + case llvmintrinsic::fp_sin: + case llvmintrinsic::fp_cos: + case llvmintrinsic::fp_tan: + case llvmintrinsic::n_intrinsic: /* n_intrinsic: not reachable */ + break; + } + + /* At least as of 18.1.5, LLVM needs us to supply function type + * when making a function call. In particular it doesn't remember + * the function type with each function pointer + */ + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); + + return ir_builder.CreateCall(llvm_fn_type, + llvm_fnval, + args, + "calltmp"); + + } /*codegen_apply*/ + + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + llvm::AllocaInst * + MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, + const std::string & var_name, + TypeDescr var_type) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn), + xtag("var_name", var_name), + xtag("var_type", var_type->short_name())); + + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), + llvm_fn->getEntryBlock().begin()); + + llvm::Type * llvm_var_type = td_to_llvm_type(llvm_cx_.borrow(), + var_type); + + log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); + if (log) { + std::string llvm_var_type_str; + llvm::raw_string_ostream ss(llvm_var_type_str); + llvm_var_type->print(ss); + + log(xtag("llvm_var_type", llvm_var_type_str)); + } + + if (!llvm_var_type) + return nullptr; + + llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(llvm_var_type, + nullptr, + var_name); + log && log(xtag("alloca", (void*)retval), + xtag("align", retval->getAlign().value()), + xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); + + return retval; + } /*create_entry_block_alloca*/ + + + std::vector> + MachPipeline::find_lambdas(ref::brw expr) const + { + std::vector> retval_v; + + expr->visit_preorder( + [&retval_v](ref::brw x) + { + if (x->extype() == exprtype::lambda) { + retval_v.push_back(Lambda::from(x)); + } + }); + + return retval_v; + } /*find_lambdas*/ + + llvm::Function * + MachPipeline::codegen_lambda_decl(ref::brw lambda) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * fn = llvm_module_->getFunction(lambda->name()); + + if (fn) { + return fn; + } + + /* establish prototype for this function */ + +#ifdef OBSOLETE + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_retval()); + + std::vector arg_type_v(lambda->n_arg()); + + for (size_t i = 0, n = lambda->n_arg(); i < n; ++i) { + arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_arg(i)); + } +#endif + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), + lambda->valuetype()); + + /* create (initially empty) function */ + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + lambda->name(), + llvm_module_.get()); + /* also capture argument names */ + { + int i = 0; + for (auto & arg : fn->args()) { + log && log("llvm formal param names", + xtag("i", i), + xtag("param", lambda->argv().at(i))); + + arg.setName(lambda->argv().at(i)->name()); + ++i; + } + } + + return fn; + } /*codegen_lambda_decl*/ + + llvm::Function * + MachPipeline::codegen_lambda_defn(ref::brw lambda, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * llvm_fn = llvm_module_->getFunction(lambda->name()); + + if (!llvm_fn) { + /** function with this name not declared? **/ + cerr << "MachPipeline::codegen_lambda: function f not declared" + << xtag("f", lambda->name()) + << endl; + + return nullptr; + } + + + /* generate function body */ + + auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); + + ir_builder.SetInsertPoint(block); + + /** Actual parameters will need their own activation record. + * Track its shape here. + **/ + this->env_stack_.push(activation_record()); + + { + log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); + + int i = 0; + for (auto & arg : llvm_fn->args()) { + log && log("nested environment", + xtag("i", i), + xtag("param", std::string(arg.getName()))); + + std::string arg_name = std::string(arg.getName()); + + /* stack location for arg[i] */ + llvm::AllocaInst * alloca + = create_entry_block_alloca(llvm_fn, + arg_name, + lambda->fn_arg(i)); + + if (!alloca) { + this->env_stack_.pop(); + return nullptr; + } + + /* store on function entry + * see codegen_variable() for corresponding load + */ + ir_builder.CreateStore(&arg, alloca); + + /* remember stack location for reference + assignment + * in lambda body. + * + */ + env_stack_.top().alloc_var(arg_name, alloca); + ++i; + } + } + + llvm::Value * retval = this->codegen(lambda->body(), ir_builder); + + if (retval) { + /* completes the function.. */ + ir_builder.CreateRet(retval); + + /* validate! always validate! */ + llvm::verifyFunction(*llvm_fn); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + llvm_fn->print(ss); + + log(xtag("IR-before-opt", buf)); + } + + /* optimize! improves IR */ + ir_pipeline_->run_pipeline(*llvm_fn); // llvm_fpmgr_->run(*llvm_fn, *llvm_famgr_); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + llvm_fn->print(ss); + + log(xtag("IR-after-opt", buf)); + } + } else { + /* oops, something went wrong */ + llvm_fn->eraseFromParent(); + + llvm_fn = nullptr; + } + + this->env_stack_.pop(); + + log && log("after pop, env stack size Z", xtag("Z", env_stack_.size())); + + return llvm_fn; + } /*codegen_lambda_defn*/ + + llvm::Value * + MachPipeline::codegen_variable(ref::brw var, + llvm::IRBuilder<> & ir_builder) + { + if (env_stack_.empty()) { + cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" + << xtag("x", var->name()) + << endl; + + return nullptr; + } + + llvm::AllocaInst * alloca = env_stack_.top().lookup_var(var->name()); + + if (!alloca) + return nullptr; + + /* code to load value from stack */ + return ir_builder.CreateLoad(alloca->getAllocatedType(), + alloca, + var->name().c_str()); + } /*codegen_variable*/ + + llvm::Value * + MachPipeline::codegen_ifexpr(ref::brw expr, llvm::IRBuilder<> & ir_builder) + { + llvm::Value * test_ir = this->codegen(expr->test(), ir_builder); + + /** need test result in a variable **/ + llvm::Value * test_with_cmp_ir + = ir_builder.CreateFCmpONE(test_ir, + llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(0.0)), + "iftest"); + + llvm::Function * parent_fn = ir_builder.GetInsertBlock()->getParent(); + + /* when_true_bb, when_false_bb, merge_bb: + * initially-empty basic-blocks for {when_true, when_false, merged} codegen + */ + + /* when_true branch inserted at (current) end of function */ + llvm::BasicBlock * when_true_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_true", + parent_fn); + llvm::BasicBlock * when_false_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_false"); + + llvm::BasicBlock * merge_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "merge"); + + /* IR to direct control flow to one of {when_true_bb, when_false_bb}, + * depending on result of test_with_cmp_ir + */ + ir_builder.CreateCondBr(test_with_cmp_ir, + when_true_bb, + when_false_bb); + + /* populate when_true_bb */ + ir_builder.SetInsertPoint(when_true_bb); + + llvm::Value * when_true_ir = this->codegen(expr->when_true(), + ir_builder); + + if (!when_true_ir) + return nullptr; + + /* at end of when-true sequence, jump to merge suffix */ + ir_builder.CreateBr(merge_bb); + /* note: codegen for expr->when_true() may have altered builder's "current block" */ + when_true_bb = ir_builder.GetInsertBlock(); + + /* populate when_false_bb */ + parent_fn->insert(parent_fn->end(), when_false_bb); + ir_builder.SetInsertPoint(when_false_bb); + + llvm::Value * when_false_ir = this->codegen(expr->when_false(), ir_builder); + if (!when_false_ir) + return nullptr; + + /* at end of when-false sequence, jump to merge suffix */ + ir_builder.CreateBr(merge_bb); + /* note: codegen for expr->when_false() may have altered builder's "current block" */ + when_false_bb = ir_builder.GetInsertBlock(); + + /* merged suffix sequence */ + parent_fn->insert(parent_fn->end(), merge_bb); + ir_builder.SetInsertPoint(merge_bb); + + /** TODO: switch to getInt1Ty here **/ + llvm::PHINode * phi_node + = ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), + 2 /*#of branches being merged (?)*/, + "iftmp"); + phi_node->addIncoming(when_true_ir, when_true_bb); + phi_node->addIncoming(when_false_ir, when_false_bb); + + return phi_node; + } /*codegen_ifexpr*/ + + llvm::Value * + MachPipeline::codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder) + { + switch(expr->extype()) { + case exprtype::constant: + return this->codegen_constant(ConstantInterface::from(expr)); + case exprtype::primitive: + return this->codegen_primitive(PrimitiveInterface::from(expr)); + case exprtype::apply: + return this->codegen_apply(Apply::from(expr), ir_builder); + case exprtype::lambda: + return this->codegen_lambda_decl(Lambda::from(expr)); + case exprtype::variable: + return this->codegen_variable(Variable::from(expr), ir_builder); + case exprtype::ifexpr: + return this->codegen_ifexpr(IfExpr::from(expr), ir_builder); + case exprtype::invalid: + case exprtype::n_expr: + return nullptr; + break; + } + + cerr << "MachPipeline::codegen: error: no handler for expression of type T" + << xtag("T", expr->extype()) + << endl; + + return nullptr; + } /*codegen*/ + + llvm::Value * + MachPipeline::codegen_toplevel(ref::brw expr) + { + /* - Pass 1. + * get set of lambdas. + * Generate decls for all. + * + * TODO: for lexical scoping (not implemented yet) + * will need traversal that maintains stack + * of ancestor lambdas, or at least their + * activation records. May want to generalize + * activation_record so we can track the set of variables + * before generating AllocaInst's. + * + * - Pass 2. + * Generate code for lambdas. + * + * - Pass 3. + * If toplevel expressions isn't a lambda + * (? won't make sense at present when called from python) + * generate code for it too + */ + + /* Pass 1. */ + auto fn_v = this->find_lambdas(expr); + + for (auto lambda : fn_v) { + this->codegen_lambda_decl(lambda); + } + + /* Pass 2 */ + for (auto lambda : fn_v) { + this->codegen_lambda_defn(lambda, + *(this->llvm_toplevel_ir_builder_.get())); + } + + /* Pass 3 */ + if (expr->extype() == exprtype::lambda) { + /* code already generated in pass 2; + * look it up + */ + + return llvm_module_->getFunction(Lambda::from(expr)->name()); + } else { + /* toplevel expression isn't a lambda, + * so code for it hasn't been generated. + * Do that now + */ + return this->codegen(expr, + *(this->llvm_toplevel_ir_builder_.get())); + } + } /*codegen_toplevel*/ + + void + MachPipeline::dump_current_module() + { + /* dump module contents to console */ + + llvm_module_->dump(); + } + + void + MachPipeline::machgen_current_module() + { + static llvm::ExitOnError llvm_exit_on_err; + + auto tracker = this->jit_->dest_dynamic_lib_ref().createResourceTracker(); + + /* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create + * + * Note that @ref ir_pipeline_ holds reference, which is invalidated here + */ + auto ts_module = llvm::orc::ThreadSafeModule(std::move(llvm_module_), + std::move(llvm_cx_->llvm_cx())); + + /* note does not discard llvm_cx_->llvm_cx(), it's already been moved */ + this->llvm_cx_ = nullptr; + + llvm_exit_on_err(this->jit_->add_llvm_module(std::move(ts_module), tracker)); + + this->recreate_llvm_ir_pipeline(); + } /*machgen_current_module*/ + + std::string_view + MachPipeline::mangle(const std::string & sym) const + { + return this->jit_->mangle(sym); + } /*mangle*/ + + llvm::Expected + MachPipeline::lookup_symbol(const std::string & sym) + { + /* llvm_sym: ExecutorSymbolDef */ + auto llvm_sym_expected = this->jit_->lookup(sym); + + if (llvm_sym_expected) { + auto llvm_addr = llvm_sym_expected.get().getAddress(); + + return llvm_addr; + } else { + return llvm_sym_expected.takeError(); + } + } /*lookup_symbol*/ + + void + MachPipeline::display(std::ostream & os) const { + os << ""; + } + + std::string + MachPipeline::display_string() const { + return tostr(*this); + } + } /*namespace jit*/ +} /*namespace xo*/ + +/* end MachPipeline.cpp */ diff --git a/src/jit/activation_record.new.cpp b/src/jit/activation_record.new.cpp new file mode 100644 index 00000000..cc1aaae3 --- /dev/null +++ b/src/jit/activation_record.new.cpp @@ -0,0 +1,47 @@ +/* @file activation_record.cpp */ + +#include "activation_record.hpp" +#include "xo/indentlog/print/tag.hpp" +#include + +namespace xo { + namespace jit { + using std::cerr; + using std::endl; + + int32_t + activation_record::lookup_var(const std::string & x) const { + + auto ix = name2ix_map_.find(x); + + if (ix == name2ix_map_.end()) { + cerr << "activation_record::lookup_var: no binding for variable x" + << xtag("x", x) + << endl; + return -1; + } + + return ix->second; + } /*lookup_var*/ + +#ifdef OBSOLETE + llvm::AllocaInst * + activation_record::alloc_var(const std::string & x, + llvm::AllocaInst * alloca) + { + if (frame_.find(x) != frame_.end()) { + cerr << "activation_record::alloc_var: variable x already present in frame" + << xtag("x", x) + << endl; + return nullptr; + } + + frame_[x] = alloca; + return alloca; + } /*alloc_var*/ +#endif + } /*namespace jit*/ +} /*namespace xo*/ + + +/* end activation_record.cpp */ diff --git a/src/jit/activation_record.orig.cpp b/src/jit/activation_record.orig.cpp new file mode 100644 index 00000000..c7f40362 --- /dev/null +++ b/src/jit/activation_record.orig.cpp @@ -0,0 +1,45 @@ +/* @file activation_record.cpp */ + +#include "activation_record.hpp" +#include "xo/indentlog/print/tag.hpp" +#include + +namespace xo { + namespace jit { + using std::cerr; + using std::endl; + + llvm::AllocaInst * + activation_record::lookup_var(const std::string & x) const { + + auto ix = frame_.find(x); + + if (ix == frame_.end()) { + cerr << "activation_record::lookup_var: no binding for variable x" + << xtag("x", x) + << endl; + return nullptr; + } + + return ix->second; + } /*lookup_var*/ + + llvm::AllocaInst * + activation_record::alloc_var(const std::string & x, + llvm::AllocaInst * alloca) + { + if (frame_.find(x) != frame_.end()) { + cerr << "activation_record::alloc_var: variable x already present in frame" + << xtag("x", x) + << endl; + return nullptr; + } + + frame_[x] = alloca; + return alloca; + } /*alloc_var*/ + } /*namespace jit*/ +} /*namespace xo*/ + + +/* end activation_record.cpp */ From ebbe45e5e72f1d2db779613886ea0c62b9df6ea7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 May 2025 00:02:02 -0500 Subject: [PATCH 1545/2524] xo-flatstring: comment consistency --- include/xo/flatstring/flatstring.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/xo/flatstring/flatstring.hpp b/include/xo/flatstring/flatstring.hpp index 60828f7e..a332950d 100644 --- a/include/xo/flatstring/flatstring.hpp +++ b/include/xo/flatstring/flatstring.hpp @@ -491,7 +491,7 @@ namespace xo { ///@} }; - /** @brief sentinel type, for forbidden stringliteral with no space for a null terminator **/ + /** @brief sentinel type, for forbidden flatstring with no space for a null terminator **/ template <> struct flatstring<0> { flatstring() = delete; }; @@ -584,7 +584,7 @@ namespace xo { * * Example: * @code - * constexpr auto cmp = flatstring_compare(stringliteral("foo"), stringliteral("bar")); + * constexpr auto cmp = flatstring_compare(flatstring("foo"), flatstring("bar")); * static_assert(cmp > 0); * @endcode **/ @@ -638,4 +638,4 @@ namespace xo { ///@} } /*namespace xo*/ -/** end stringliteral.hpp **/ +/** end flatstring.hpp **/ From 5ac3c03a0ccfa8e4e320955b2589423a17c5e5ba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 May 2025 00:03:08 -0500 Subject: [PATCH 1546/2524] xo-expression: + captured-var methods --- include/xo/expression/Lambda.hpp | 2 ++ include/xo/expression/LocalEnv.hpp | 1 + 2 files changed, 3 insertions(+) diff --git a/include/xo/expression/Lambda.hpp b/include/xo/expression/Lambda.hpp index 668d1303..452cafc9 100644 --- a/include/xo/expression/Lambda.hpp +++ b/include/xo/expression/Lambda.hpp @@ -40,7 +40,9 @@ namespace xo { const std::vector> & argv() const { return local_env_->argv(); } const rp & body() const { return body_; } + const std::string& i_argname(int i_arg) const { return local_env_->lookup_arg(i_arg)->name(); } bool needs_closure_flag() const { return !free_var_set_.empty(); } + bool is_captured(const std::string& var) const { return (captured_var_set_.find(var) != captured_var_set_.end()); } // ----- FunctionInterface ----- diff --git a/include/xo/expression/LocalEnv.hpp b/include/xo/expression/LocalEnv.hpp index d4facdba..603bbfda 100644 --- a/include/xo/expression/LocalEnv.hpp +++ b/include/xo/expression/LocalEnv.hpp @@ -32,6 +32,7 @@ namespace xo { Lambda * origin() const { return origin_; } const std::vector> & argv() const { return argv_; } + const rp& lookup_arg(int i) const { return argv_[i]; } int n_arg() const { return argv_.size(); } TypeDescr fn_arg(uint32_t i) const { return argv_[i]->valuetype(); } From f510700b9945ee3fa5e05e93c7dbd796dca78e35 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 May 2025 12:04:18 -0500 Subject: [PATCH 1547/2524] xo-cmake: + subsystem-list + xo-build improvements --- CMakeLists.txt | 7 ++++ bin/xo-build.in | 92 +++++++++++++++++++++--------------------- bin/xo-cmake-config.in | 8 +++- etc/xo/subsystem-list | 38 +++++++++++++++++ 4 files changed, 99 insertions(+), 46 deletions(-) create mode 100644 etc/xo/subsystem-list diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b7c1036..14226afa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,13 @@ install( DESTINATION ${CMAKE_INSTALL_BINDIR} ) +install( + FILES + "etc/xo/subsystem-list" + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_DATADIR}/etc/xo +) + # The cmake template gen-ccov.in should be expanded in downstream project; # to pickup downstream project's PROJECT_SOURCE_DIR / PROJECT_BINARY_DIR # diff --git a/bin/xo-build.in b/bin/xo-build.in index 49bbf4d6..194ce3df 100644 --- a/bin/xo-build.in +++ b/bin/xo-build.in @@ -1,7 +1,12 @@ #!/usr/bin/env bash usage() { - echo "$0 [-u|--usage|-h|--help] [--xoname=NAME] [--repo]" + cat < 0 ]]; do case "$1" in @@ -45,6 +54,20 @@ while [[ $# > 0 ]]; do -n) noop_flag=1 ;; + -S) + shift + pathtosource=$1 + ;; + -S=*) + pathtosource="${1#*=}" + ;; + -B) + shift + pathtobuild=$1 + ;; + -B=*) + pathtobuild="${1#*=}" + ;; --list) cmd='list' ;; @@ -78,21 +101,20 @@ while [[ $# > 0 ]]; do shift done +echo xoname=$xoname pathtosource=$pathtosource pathtobuild=$pathtobuild + +if [[ -z "$pathtosource" ]]; then + pathtosource=$xoname +fi + +if [[ -z "$pathtobuild" ]]; then + pathtobuild=$xoname/.build +fi + +SUBSYSTEMLIST_FILE=@CMAKE_INSTALL_FULL_DATADIR@/etc/xo/subsystem-list + subsystem_list() { - cat <&2 echo "$0: unknown xo component [${xoname}]" + return 1 + fi esac return 0 @@ -183,7 +185,7 @@ fi if [[ $configure_flag -eq 1 ]]; then if [[ -n "$xoname" ]]; then - cmd="cmake -DCMAKE_INSTALL_PREFIX=@CMAKE_INSTALL_PREFIX@ -S $xoname -B $xoname/.build" + cmd="cmake -DCMAKE_INSTALL_PREFIX=@CMAKE_INSTALL_PREFIX@ -S $pathtosource -B $pathtobuild" if [[ $noop_flag -eq 1 ]]; then echo $cmd @@ -195,7 +197,7 @@ fi if [[ $build_flag -eq 1 ]]; then if [[ -n "$xoname" ]]; then - cmd="cmake --build $xoname/.build -j" + cmd="cmake --build $pathtobuild -j" if [[ $noop_flag -eq 1 ]]; then echo $cmd @@ -207,7 +209,7 @@ fi if [[ $install_flag -eq 1 ]]; then if [[ -n "$xoname" ]]; then - cmd="cmake --install $xoname/.build" + cmd="cmake --install $pathtobuild" if [[ $noop_flag -eq 1 ]]; then echo $cmd diff --git a/bin/xo-cmake-config.in b/bin/xo-cmake-config.in index 4950617e..bbea1361 100755 --- a/bin/xo-cmake-config.in +++ b/bin/xo-cmake-config.in @@ -1,7 +1,7 @@ #!/usr/bin/env bash usage() { - echo "$0 [-u|--usage|-h|--help|--lcov-exe|--genhtml-exe|--lcov-harness-exe|--gen-ccov-template|--cmake-module-path]" 1>&2 + echo "$0 [-u|--usage|-h|--help|--lcov-exe|--genhtml-exe|--lcov-harness-exe|--gen-ccov-template|--cmake-module-path|--subsystem-list]" 1>&2 } help() { @@ -20,6 +20,7 @@ Options: --lcov-harness-exe report path to 'xo-cmake-lcov-harness' executable --gen-ccov-template report path to 'gen-ccov.in' template --doxygen-template report path to 'Doxyfile.in' template + --subsystem-list report path to 'subsystem-ilst' EOF } @@ -50,6 +51,9 @@ while [[ $# > 0 ]]; do --doxygen-template) cmd='doxygen_template' ;; + --subsystem-list) + cmd='subsystem_list' + ;; *) usage exit 1 @@ -77,4 +81,6 @@ elif [[ $cmd == 'gen_ccov_template' ]]; then echo -n @CMAKE_INSTALL_FULL_DATADIR@/xo-macros/gen-ccov.in elif [[ $cmd == 'doxygen_template' ]]; then echo -n @CMAKE_INSTALL_FULL_DATADIR@/xo-macros/Doxyfile.in +elif [[ $cmd == 'subsystem_list' ]]; then + echo -n @CMAKE_INSTALL_FULL_DATADIR@/etc/xo/subsystem-list fi diff --git a/etc/xo/subsystem-list b/etc/xo/subsystem-list new file mode 100644 index 00000000..d1dcf6c7 --- /dev/null +++ b/etc/xo/subsystem-list @@ -0,0 +1,38 @@ +xo-cmake +xo-indentlog +xo-refcnt +xo-subsys +xo-randomgen +xo-ordinaltree +xo-pyutil +xo-flatstring +xo-reflectutil +xo-reflect +xo-pyreflect +xo-ratio +xo-unit +xo-pyunit +xo-expression +xo-pyexpression +xo-tokenizer +xo-reader +xo-jit +xo-pyjit +xo-callback +xo-webutil +xo-pywebutil +xo-printjson +xo-pyprintjson +xo-reactor +xo-pyreactor +xo-websock +xo-pywebsock +xo-statistics +xo-distribution +xo-pydistribution +xo-simulator +xo-pysimulator +xo-process +xo-pyprocess +xo-kalmanfilter +xo-pykalmanfilter From 2f2141328ca65c5f4e2f918ed61458057198ffba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 May 2025 12:15:55 -0500 Subject: [PATCH 1548/2524] + README --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..1aa24fa9 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# Introduction + +Local nix build for xo libraries. +Intended for local development work, with source in immediate subdirectories. + +## Features + +- native c++ +- deterministic simulation +- reflection +- python bindings + +## Getting Started + +### Cmake build + +If `nix` is available, you probably prefer the nix build. +Otherwise continue reading.. + +The cmake build has two phases, because it needs to bootstrap +generated `xo-cmake-config`, `xo-build` helpers. + +``` +$ cd xo +$ PREFIX=/path/to/say/usr/local +# phase 1 +$ cmake -B .build0 -S xo-cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} +$ cmake --build .build0 +$ cmake --install .build0 +# phase 2 +$ cmake -B .build -S . -DCMAKE_INSTALL_PREFIX=${PREFIX} +$ cmake --build .build +$ cmake --install .build +``` + +### Nix Build + +Nix build uses toplevel `default.nix`, +along with top-level `pkgs/xo-foo.nix` for each subproject `foo`. +It doesn't interact with toplevel `CMakeLists.txt`. + +``` +$ nix-build -A xo-userenv +``` From 2cb4df32e9bdf66791be80fa1d58670d88a72bcb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 May 2025 12:17:19 -0500 Subject: [PATCH 1549/2524] + .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..656e515b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +result +.build* From c88807597e9025c7c6c872481c26fbe6004d4ad1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 May 2025 16:23:30 -0500 Subject: [PATCH 1550/2524] + xo-cmake build --- CMakeLists.txt | 104 ++++++++++++++++++++++++++++++++++++++++++++++ default.nix | 94 +++++++++++++++++++++++++++++++++++++++++ pkgs/xo-cmake.nix | 15 +++++++ 3 files changed, 213 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 default.nix create mode 100644 pkgs/xo-cmake.nix diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..2a971cce --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,104 @@ +# xo/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo-submodule3 VERSION 1.0) + +# ---------------------------------------------------------------- +# global build settings + +# Adopting submodule builds directly into this cmake. +# Submodule builds will pickup dependent xo artifacts directly +# from sibling build dirs. +# (Contrast with a build that relies on install step). +# In particular, configure step in satellite projects +# needs to avoid using cmake find_package() on sibling xo projects: +# 1. .cmake support files +# fooConfig.cmake +# fooConfigVersion.cmake +# fooTargets.cmake +# won't have been installed +# 2. In any case, they point to final install location; +# we need build location +# +set(XO_SUBMODULE_BUILD True) + +# toplevel source directory; used only with XO_SUBMODULE_BUILD +set(XO_UMBRELLA_SOURCE_DIR ${CMAKE_SOURCE_DIR}) +set(XO_UMBRELLA_REPO_SUBDIR .) +# toplevel binary directory; used only with XO_SUBMODULE_BUILD +set(XO_UMBRELLA_BINARY_DIR ${CMAKE_BINARY_DIR}) + +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/xo-cmake/cmake) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() + +# ---------------------------------------------------------------- +# global c++ settings. + +enable_language(CXX) + +# temporary compiler flags here +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) +endif() + +set(CMAKE_CXX_STANDARD_REQUIRED True) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") + +if(NOT CMAKE_INSTALL_RPATH) + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath for installed libraries/executables") +endif() + +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_INSTALL_RPATH=${CMAKE_INSTALL_RPATH}") + +# ---------------------------------------------------------------- +# xo satellite projects + +add_subdirectory(xo-cmake) +#add_subdirectory(xo-indentlog) +#add_subdirectory(xo-refcnt) +#add_subdirectory(xo-subsys) +#add_subdirectory(xo-randomgen) +#add_subdirectory(xo-ordinaltree) +#add_subdirectory(xo-pyutil) +#add_subdirectory(xo-flatstring) +#add_subdirectory(xo-reflectutil) +#add_subdirectory(xo-reflect) +#add_subdirectory(xo-pyreflect) +#add_subdirectory(xo-ratio) +#add_subdirectory(xo-unit) +#add_subdirectory(xo-pyunit) +#add_subdirectory(xo-expression) +#add_subdirectory(xo-pyexpression) +#add_subdirectory(xo-tokenizer) +#add_subdirectory(xo-reader) +#add_subdirectory(xo-jit) +#add_subdirectory(xo-pyjit) +#add_subdirectory(xo-callback) +#add_subdirectory(xo-webutil) +#add_subdirectory(xo-pywebutil) +#add_subdirectory(xo-printjson) +#add_subdirectory(xo-pyprintjson) +#add_subdirectory(xo-reactor) +#add_subdirectory(xo-pyreactor) +#add_subdirectory(xo-websock) +#add_subdirectory(xo-pywebsock) +#add_subdirectory(xo-statistics) +#add_subdirectory(xo-distribution) +#add_subdirectory(xo-pydistribution) +#add_subdirectory(xo-simulator) +#add_subdirectory(xo-pysimulator) +#add_subdirectory(xo-process) +#add_subdirectory(xo-pyprocess) +#add_subdirectory(xo-kalmanfilter) +#add_subdirectory(xo-pykalmanfilter) diff --git a/default.nix b/default.nix new file mode 100644 index 00000000..85998608 --- /dev/null +++ b/default.nix @@ -0,0 +1,94 @@ +#{ pkgs ? import { overlays = [ (final: prev: { llvmPackages = prev.llvmPackages_17; }) ]; } }: +#pkgs.mkShell { +# buildInputs = [ pkgs.coreutils ]; +#} + + +{ + nixpkgs-path ? ../nixpkgs, + +# pkgs ? import (fetchTarball { +# # 24.05-darwin works on macos, clang17, llvm 18 (copying from xo-nix2) +# url = "https://github.com/NixOS/nixpkgs/archive/dd868b7bd4d1407d607da0d1d9c5eca89132e2f7.tar.gz"; +# }), +} : + +let + # this approach (overlays) is effective, but has super wide cross-section, + # since absolutely everything has to be rebuilt from source + # + + llvm-overlay = self: super: { + # use 'super' when you want to override the terms of a package. + # use 'self' when pointing to an existing package + + llvmPackages = super.llvmPackages_18; + }; + + xo-overlay = self: super: + let + # Choose the LLVM version you want + llvmPackages = self.llvmPackages_18; + in + + let + # Rebuild stdenv to use that LLVM version + customStdenv = super.overrideCC super.stdenv llvmPackages.clang; + in + + { + xo-cmake = self.callPackage pkgs/xo-cmake.nix {}; +# xo-indentlog = self.callPackage pkgs/xo-indentlog.nix {}; +# xo-refcnt = self.callPackage pkgs/xo-refcnt.nix {}; +# xo-subsys = self.callPackage pkgs/xo-subsys.nix {}; +# xo-randomgen = self.callPackage pkgs/xo-randomgen.nix {}; +# xo-ordinaltree = self.callPackage pkgs/xo-ordinaltree.nix {}; +# xo-pyutil = self.callPackage pkgs/xo-pyutil.nix {}; +# xo-flatstring = self.callPackage pkgs/xo-flatstring.nix {}; +# xo-reflectutil = self.callPackage pkgs/xo-reflectutil.nix {}; +# xo-reflect = self.callPackage pkgs/xo-reflect.nix {}; +# xo-pyreflect = self.callPackage pkgs/xo-pyreflect.nix {}; +# xo-ratio = self.callPackage pkgs/xo-ratio.nix {}; +# xo-unit = self.callPackage pkgs/xo-unit.nix {}; +# xo-pyunit = self.callPackage pkgs/xo-pyunit.nix {}; +# xo-expression = self.callPackage pkgs/xo-expression.nix {}; +# xo-pyexpression = self.callPackage pkgs/xo-pyexpression.nix {}; +# xo-tokenizer = self.callPackage pkgs/xo-tokenizer.nix {}; +# xo-reader = self.callPackage pkgs/xo-reader.nix {}; +# xo-jit = self.callPackage pkgs/xo-jit.nix { stdenv = customStdenv; +# clang = llvmPackages.clang; +# llvm = llvmPackages.llvm; }; +# xo-pyjit = self.callPackage pkgs/xo-pyjit.nix {}; +# xo-callback = self.callPackage pkgs/xo-callback.nix {}; +# xo-webutil = self.callPackage pkgs/xo-webutil.nix {}; +# xo-pywebutil = self.callPackage pkgs/xo-pywebutil.nix {}; +# xo-printjson = self.callPackage pkgs/xo-printjson.nix {}; +# xo-pyprintjson = self.callPackage pkgs/xo-pyprintjson.nix {}; +# xo-reactor = self.callPackage pkgs/xo-reactor.nix {}; +# xo-pyreactor = self.callPackage pkgs/xo-pyreactor.nix {}; +# xo-websock = self.callPackage pkgs/xo-websock.nix {}; +# xo-pywebsock = self.callPackage pkgs/xo-pywebsock.nix {}; +# xo-statistics = self.callPackage pkgs/xo-statistics.nix {}; +# xo-distribution = self.callPackage pkgs/xo-distribution.nix {}; +# xo-pydistribution = self.callPackage pkgs/xo-pydistribution.nix {}; +# xo-simulator = self.callPackage pkgs/xo-simulator.nix {}; +# xo-pysimulator = self.callPackage pkgs/xo-pysimulator.nix {}; +# xo-process = self.callPackage pkgs/xo-process.nix {}; +# xo-pyprocess = self.callPackage pkgs/xo-pyprocess.nix {}; +# xo-kalmanfilter = self.callPackage pkgs/xo-kalmanfilter.nix {}; +# xo-pykalmanfilter = self.callPackage pkgs/xo-pykalmanfilter.nix {}; +# +# xo-userenv = self.callPackage pkgs/xo-userenv.nix {}; +# xo-userenv-slow = self.callPackage pkgs/xo-userenv-slow.nix {}; + }; + +in +let + pkgs = import nixpkgs-path { + overlays = [ +# llvm-overlay + xo-overlay + ]; + }; +in +pkgs diff --git a/pkgs/xo-cmake.nix b/pkgs/xo-cmake.nix new file mode 100644 index 00000000..69c32bab --- /dev/null +++ b/pkgs/xo-cmake.nix @@ -0,0 +1,15 @@ +{ + # dependencies + + stdenv, + cmake +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-cmake"; + + src = ../xo-cmake; + + nativeBuildInputs = [ cmake ]; + }) From 81f59ed21754119944dc8375a88840e9506ad768 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 May 2025 16:30:13 -0500 Subject: [PATCH 1551/2524] + xo-userenv --- README.md | 41 +++++++++++++++++++ default.nix | 2 +- pkgs/xo-userenv.nix | 99 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 pkgs/xo-userenv.nix diff --git a/README.md b/README.md index 1aa24fa9..09a6f261 100644 --- a/README.md +++ b/README.md @@ -42,3 +42,44 @@ It doesn't interact with toplevel `CMakeLists.txt`. ``` $ nix-build -A xo-userenv ``` + +This builds all xo subprojects, assembles sandbox under `./result`. + +``` +$ tree -L 1 ./result +./result +├── bin +│   ├── xo-build +│   ├── xo-cmake-config +│   └── xo-cmake-lcov-harness +└── share + ├── cmake + │   └── xo_macros + │   ├── code-coverage.cmake + │   ├── xo-project-macros.cmake + │   └── xo_cxx.cmake + ├── etc + │   └── xo + │   └── subsystem-list + └── xo-macros + ├── Doxyfile.in + ├── gen-ccov.in + └── xo-bootstrap-macros.cmake +``` + +## To add a new satellite repo + +1. check clone in clean state (all local changes committed or unwound) + +2. add satellite as remote + +``` +$ git remote add xo-foo git@github.com:Rconybea/xo-foo.git +$ git fetch xo-foo +``` + +3. checkout satellite repo + +``` +$ git subtree add --prefix=xo-foo main +``` diff --git a/default.nix b/default.nix index 85998608..3c2a1aae 100644 --- a/default.nix +++ b/default.nix @@ -78,7 +78,7 @@ let # xo-kalmanfilter = self.callPackage pkgs/xo-kalmanfilter.nix {}; # xo-pykalmanfilter = self.callPackage pkgs/xo-pykalmanfilter.nix {}; # -# xo-userenv = self.callPackage pkgs/xo-userenv.nix {}; + xo-userenv = self.callPackage pkgs/xo-userenv.nix {}; # xo-userenv-slow = self.callPackage pkgs/xo-userenv-slow.nix {}; }; diff --git a/pkgs/xo-userenv.nix b/pkgs/xo-userenv.nix new file mode 100644 index 00000000..541dc999 --- /dev/null +++ b/pkgs/xo-userenv.nix @@ -0,0 +1,99 @@ +# builds environment with all xo packages, +# using combined output directory for each. +# +# parallels github actions build on stock ubuntu, +# except that we use nixpkgs for toolchain +# +# For xo development, probably prefer xo-userenv.nix instead of this xo-userenv-slow.nix: +# 1. xo-userenv.nix allows parallel build +# 2. xo-userenv.nix only rebuilds xo packages that have changed + +{ + # nixpkgs dependencies + buildEnv, + stdenv, + cmake, + catch2, + eigen, + libwebsockets, + jsoncpp, + doxygen, + sphinx, + python3Packages, + + # xo dependencies + xo-cmake, +# xo-indentlog, +# xo-subsys, +# xo-refcnt, +# xo-randomgen, +# xo-ordinaltree, +# xo-flatstring, +# xo-reflectutil, +# xo-ratio, +# xo-unit, +# xo-pyunit, +# xo-pyutil, +# xo-reflect, +# xo-pyreflect, +# xo-printjson, +# xo-pyprintjson, +# xo-callback, +# xo-webutil, +# xo-pywebutil, +# xo-reactor, +# xo-pyreactor, +# xo-simulator, +# xo-pysimulator, xo-distribution, xo-pydistribution, xo-process, xo-pyprocess, xo-statistics, xo-kalmanfilter, +# xo-pykalmanfilter, xo-websock, xo-pywebsock, xo-tokenizer, +# xo-expression, xo-pyexpression, xo-reader, +# xo-jit, +# xo-pyjit + + # other args + + # someconfigurationoption ? false +} : + +buildEnv { + name = "xo-userenv"; + paths = [ xo-cmake +# xo-indentlog +# xo-refcnt +# xo-subsys +# xo-randomgen +# xo-ordinaltree +# xo-pyutil +# xo-flatstring +# xo-reflectutil +# xo-reflect +# xo-pyreflect +# xo-ratio +# xo-unit +# xo-pyunit +# xo-expression +# xo-pyexpression +# xo-tokenizer +# xo-reader +# xo-jit +# xo-pyjit +# xo-callback +# xo-webutil +# xo-pywebutil +# xo-printjson +# xo-pyprintjson +# xo-reactor +# xo-pyreactor +# xo-websock +# xo-pywebsock +# xo-statistics +# xo-distribution +# xo-pydistribution +# xo-simulator +# xo-pysimulator +# xo-process +# xo-pyprocess +# xo-kalmanfilter +# xo-pykalmanfilter + ]; +} From 88a8cd0f0a7c02dbfa8e6682bab108df84deb03b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 May 2025 16:43:01 -0500 Subject: [PATCH 1552/2524] + github actions workflow (1st attempt) --- .github/main-stock-ubuntu.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/main-stock-ubuntu.yml diff --git a/.github/main-stock-ubuntu.yml b/.github/main-stock-ubuntu.yml new file mode 100644 index 00000000..8c110eec --- /dev/null +++ b/.github/main-stock-ubuntu.yml @@ -0,0 +1,33 @@ +name: build on ubuntu base platform + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize CMake build type here + BUILD_TYPE: Release + +jobs: + build: + # This build won't be entirely reproducible, given ubuntu changes on github runners may + # introduce regressions. + # + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: bootstrap xo-cmake + run: | + PREFIX=${{github.workspace}//local + mkdir -p ${PREFIX} + echo "::group::xo-cmake configure" + cmake -B .build0 -S xo-cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} + echo "::group::xo-cmake build" + cmake --build .build0 + echo "::group::xo-cmake install" + cmake --install .build0 From a0fed9a4d220e6b37fcfbe86230b136eede43118 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 May 2025 16:45:22 -0500 Subject: [PATCH 1553/2524] bugfix: actions .yml location --- .github/{ => workflows}/main-stock-ubuntu.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{ => workflows}/main-stock-ubuntu.yml (100%) diff --git a/.github/main-stock-ubuntu.yml b/.github/workflows/main-stock-ubuntu.yml similarity index 100% rename from .github/main-stock-ubuntu.yml rename to .github/workflows/main-stock-ubuntu.yml From cf62cb328a9859f20c682acc4708bd2435407fe6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 May 2025 16:46:30 -0500 Subject: [PATCH 1554/2524] typo: missed } --- .github/workflows/main-stock-ubuntu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-stock-ubuntu.yml b/.github/workflows/main-stock-ubuntu.yml index 8c110eec..af8491d7 100644 --- a/.github/workflows/main-stock-ubuntu.yml +++ b/.github/workflows/main-stock-ubuntu.yml @@ -23,7 +23,7 @@ jobs: - name: bootstrap xo-cmake run: | - PREFIX=${{github.workspace}//local + PREFIX=${{github.workspace}}//local mkdir -p ${PREFIX} echo "::group::xo-cmake configure" cmake -B .build0 -S xo-cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} From 204e600e14cb32ef235d713a5547506d11c372f2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 May 2025 16:48:26 -0500 Subject: [PATCH 1555/2524] github: print final install tree --- .github/workflows/main-stock-ubuntu.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main-stock-ubuntu.yml b/.github/workflows/main-stock-ubuntu.yml index af8491d7..d4e5b85b 100644 --- a/.github/workflows/main-stock-ubuntu.yml +++ b/.github/workflows/main-stock-ubuntu.yml @@ -31,3 +31,7 @@ jobs: cmake --build .build0 echo "::group::xo-cmake install" cmake --install .build0 + + - name: print install path + run: | + tree ${{github.workspace}}/local From 624178f1932508a687d85ddea56d03998193207a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 May 2025 16:54:17 -0500 Subject: [PATCH 1556/2524] + xo-indentlog build --- .github/workflows/main-stock-ubuntu.yml | 2 +- CMakeLists.txt | 2 +- default.nix | 2 +- pkgs/xo-indentlog.nix | 18 ++++++++++++++++++ pkgs/xo-userenv.nix | 4 ++-- 5 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 pkgs/xo-indentlog.nix diff --git a/.github/workflows/main-stock-ubuntu.yml b/.github/workflows/main-stock-ubuntu.yml index d4e5b85b..7871b808 100644 --- a/.github/workflows/main-stock-ubuntu.yml +++ b/.github/workflows/main-stock-ubuntu.yml @@ -23,7 +23,7 @@ jobs: - name: bootstrap xo-cmake run: | - PREFIX=${{github.workspace}}//local + PREFIX=${{github.workspace}}/local mkdir -p ${PREFIX} echo "::group::xo-cmake configure" cmake -B .build0 -S xo-cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a971cce..c76226c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,7 @@ message("-- CMAKE_INSTALL_RPATH=${CMAKE_INSTALL_RPATH}") # xo satellite projects add_subdirectory(xo-cmake) -#add_subdirectory(xo-indentlog) +add_subdirectory(xo-indentlog) #add_subdirectory(xo-refcnt) #add_subdirectory(xo-subsys) #add_subdirectory(xo-randomgen) diff --git a/default.nix b/default.nix index 3c2a1aae..5b619074 100644 --- a/default.nix +++ b/default.nix @@ -38,7 +38,7 @@ let { xo-cmake = self.callPackage pkgs/xo-cmake.nix {}; -# xo-indentlog = self.callPackage pkgs/xo-indentlog.nix {}; + xo-indentlog = self.callPackage pkgs/xo-indentlog.nix {}; # xo-refcnt = self.callPackage pkgs/xo-refcnt.nix {}; # xo-subsys = self.callPackage pkgs/xo-subsys.nix {}; # xo-randomgen = self.callPackage pkgs/xo-randomgen.nix {}; diff --git a/pkgs/xo-indentlog.nix b/pkgs/xo-indentlog.nix new file mode 100644 index 00000000..12255020 --- /dev/null +++ b/pkgs/xo-indentlog.nix @@ -0,0 +1,18 @@ +{ + # dependencies + stdenv, cmake, catch2, + + xo-cmake, +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-indentlog"; + version = "1.0"; + + src = ../xo-indentlog; + + cmakeFlags = ["-DCMAKE_MODULE_PATH=${xo-cmake}/share/cmake"]; + doCheck = true; + nativeBuildInputs = [ cmake catch2 xo-cmake ]; + }) diff --git a/pkgs/xo-userenv.nix b/pkgs/xo-userenv.nix index 541dc999..b70bb3f6 100644 --- a/pkgs/xo-userenv.nix +++ b/pkgs/xo-userenv.nix @@ -23,7 +23,7 @@ # xo dependencies xo-cmake, -# xo-indentlog, + xo-indentlog, # xo-subsys, # xo-refcnt, # xo-randomgen, @@ -58,7 +58,7 @@ buildEnv { name = "xo-userenv"; paths = [ xo-cmake -# xo-indentlog + xo-indentlog # xo-refcnt # xo-subsys # xo-randomgen From 0df3e730a053f37eceef9ce4f5c8b2389afb09e8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 10 May 2025 16:54:38 -0500 Subject: [PATCH 1557/2524] Add 'xo-indentlog/' from commit '624178f1932508a687d85ddea56d03998193207a' git-subtree-dir: xo-indentlog git-subtree-mainline: 624178f1932508a687d85ddea56d03998193207a git-subtree-split: 624178f1932508a687d85ddea56d03998193207a --- .../.github/workflows/main-stock-ubuntu.yml | 37 + xo-indentlog/.gitignore | 2 + xo-indentlog/CMakeLists.txt | 104 + xo-indentlog/README.md | 85 + xo-indentlog/default.nix | 94 + xo-indentlog/pkgs/xo-cmake.nix | 15 + xo-indentlog/pkgs/xo-indentlog.nix | 18 + xo-indentlog/pkgs/xo-userenv.nix | 99 + .../.github/workflows/xo-cpp-main.yml | 74 + xo-indentlog/xo-cmake/.gitignore | 3 + xo-indentlog/xo-cmake/CMakeLists.txt | 67 + xo-indentlog/xo-cmake/FAQ | 120 + xo-indentlog/xo-cmake/README.md | 61 + xo-indentlog/xo-cmake/bin/xo-build.in | 220 ++ xo-indentlog/xo-cmake/bin/xo-cmake-config.in | 86 + .../xo-cmake/bin/xo-cmake-lcov-harness.in | 121 + .../cmake/xo_macros/code-coverage.cmake | 678 ++++ .../cmake/xo_macros/xo-project-macros.cmake | 2 + .../xo-cmake/cmake/xo_macros/xo_cxx.cmake | 1426 +++++++++ xo-indentlog/xo-cmake/etc/xo/subsystem-list | 38 + .../xo-cmake/share/xo-macros/Doxyfile.in | 2816 +++++++++++++++++ .../xo-cmake/share/xo-macros/gen-ccov.in | 30 + .../share/xo-macros/xo-bootstrap-macros.cmake | 35 + 23 files changed, 6231 insertions(+) create mode 100644 xo-indentlog/.github/workflows/main-stock-ubuntu.yml create mode 100644 xo-indentlog/.gitignore create mode 100644 xo-indentlog/CMakeLists.txt create mode 100644 xo-indentlog/README.md create mode 100644 xo-indentlog/default.nix create mode 100644 xo-indentlog/pkgs/xo-cmake.nix create mode 100644 xo-indentlog/pkgs/xo-indentlog.nix create mode 100644 xo-indentlog/pkgs/xo-userenv.nix create mode 100644 xo-indentlog/xo-cmake/.github/workflows/xo-cpp-main.yml create mode 100644 xo-indentlog/xo-cmake/.gitignore create mode 100644 xo-indentlog/xo-cmake/CMakeLists.txt create mode 100644 xo-indentlog/xo-cmake/FAQ create mode 100644 xo-indentlog/xo-cmake/README.md create mode 100644 xo-indentlog/xo-cmake/bin/xo-build.in create mode 100755 xo-indentlog/xo-cmake/bin/xo-cmake-config.in create mode 100755 xo-indentlog/xo-cmake/bin/xo-cmake-lcov-harness.in create mode 100644 xo-indentlog/xo-cmake/cmake/xo_macros/code-coverage.cmake create mode 100644 xo-indentlog/xo-cmake/cmake/xo_macros/xo-project-macros.cmake create mode 100644 xo-indentlog/xo-cmake/cmake/xo_macros/xo_cxx.cmake create mode 100644 xo-indentlog/xo-cmake/etc/xo/subsystem-list create mode 100644 xo-indentlog/xo-cmake/share/xo-macros/Doxyfile.in create mode 100644 xo-indentlog/xo-cmake/share/xo-macros/gen-ccov.in create mode 100644 xo-indentlog/xo-cmake/share/xo-macros/xo-bootstrap-macros.cmake diff --git a/xo-indentlog/.github/workflows/main-stock-ubuntu.yml b/xo-indentlog/.github/workflows/main-stock-ubuntu.yml new file mode 100644 index 00000000..7871b808 --- /dev/null +++ b/xo-indentlog/.github/workflows/main-stock-ubuntu.yml @@ -0,0 +1,37 @@ +name: build on ubuntu base platform + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize CMake build type here + BUILD_TYPE: Release + +jobs: + build: + # This build won't be entirely reproducible, given ubuntu changes on github runners may + # introduce regressions. + # + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: bootstrap xo-cmake + run: | + PREFIX=${{github.workspace}}/local + mkdir -p ${PREFIX} + echo "::group::xo-cmake configure" + cmake -B .build0 -S xo-cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} + echo "::group::xo-cmake build" + cmake --build .build0 + echo "::group::xo-cmake install" + cmake --install .build0 + + - name: print install path + run: | + tree ${{github.workspace}}/local diff --git a/xo-indentlog/.gitignore b/xo-indentlog/.gitignore new file mode 100644 index 00000000..656e515b --- /dev/null +++ b/xo-indentlog/.gitignore @@ -0,0 +1,2 @@ +result +.build* diff --git a/xo-indentlog/CMakeLists.txt b/xo-indentlog/CMakeLists.txt new file mode 100644 index 00000000..c76226c7 --- /dev/null +++ b/xo-indentlog/CMakeLists.txt @@ -0,0 +1,104 @@ +# xo/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo-submodule3 VERSION 1.0) + +# ---------------------------------------------------------------- +# global build settings + +# Adopting submodule builds directly into this cmake. +# Submodule builds will pickup dependent xo artifacts directly +# from sibling build dirs. +# (Contrast with a build that relies on install step). +# In particular, configure step in satellite projects +# needs to avoid using cmake find_package() on sibling xo projects: +# 1. .cmake support files +# fooConfig.cmake +# fooConfigVersion.cmake +# fooTargets.cmake +# won't have been installed +# 2. In any case, they point to final install location; +# we need build location +# +set(XO_SUBMODULE_BUILD True) + +# toplevel source directory; used only with XO_SUBMODULE_BUILD +set(XO_UMBRELLA_SOURCE_DIR ${CMAKE_SOURCE_DIR}) +set(XO_UMBRELLA_REPO_SUBDIR .) +# toplevel binary directory; used only with XO_SUBMODULE_BUILD +set(XO_UMBRELLA_BINARY_DIR ${CMAKE_BINARY_DIR}) + +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/xo-cmake/cmake) + +# ---------------------------------------------------------------- +# unit test setup + +enable_testing() + +# ---------------------------------------------------------------- +# global c++ settings. + +enable_language(CXX) + +# temporary compiler flags here +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) +endif() + +set(CMAKE_CXX_STANDARD_REQUIRED True) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") + +if(NOT CMAKE_INSTALL_RPATH) + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING "runpath for installed libraries/executables") +endif() + +message("-- CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") +message("-- CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +message("-- CMAKE_INSTALL_RPATH=${CMAKE_INSTALL_RPATH}") + +# ---------------------------------------------------------------- +# xo satellite projects + +add_subdirectory(xo-cmake) +add_subdirectory(xo-indentlog) +#add_subdirectory(xo-refcnt) +#add_subdirectory(xo-subsys) +#add_subdirectory(xo-randomgen) +#add_subdirectory(xo-ordinaltree) +#add_subdirectory(xo-pyutil) +#add_subdirectory(xo-flatstring) +#add_subdirectory(xo-reflectutil) +#add_subdirectory(xo-reflect) +#add_subdirectory(xo-pyreflect) +#add_subdirectory(xo-ratio) +#add_subdirectory(xo-unit) +#add_subdirectory(xo-pyunit) +#add_subdirectory(xo-expression) +#add_subdirectory(xo-pyexpression) +#add_subdirectory(xo-tokenizer) +#add_subdirectory(xo-reader) +#add_subdirectory(xo-jit) +#add_subdirectory(xo-pyjit) +#add_subdirectory(xo-callback) +#add_subdirectory(xo-webutil) +#add_subdirectory(xo-pywebutil) +#add_subdirectory(xo-printjson) +#add_subdirectory(xo-pyprintjson) +#add_subdirectory(xo-reactor) +#add_subdirectory(xo-pyreactor) +#add_subdirectory(xo-websock) +#add_subdirectory(xo-pywebsock) +#add_subdirectory(xo-statistics) +#add_subdirectory(xo-distribution) +#add_subdirectory(xo-pydistribution) +#add_subdirectory(xo-simulator) +#add_subdirectory(xo-pysimulator) +#add_subdirectory(xo-process) +#add_subdirectory(xo-pyprocess) +#add_subdirectory(xo-kalmanfilter) +#add_subdirectory(xo-pykalmanfilter) diff --git a/xo-indentlog/README.md b/xo-indentlog/README.md new file mode 100644 index 00000000..09a6f261 --- /dev/null +++ b/xo-indentlog/README.md @@ -0,0 +1,85 @@ +# Introduction + +Local nix build for xo libraries. +Intended for local development work, with source in immediate subdirectories. + +## Features + +- native c++ +- deterministic simulation +- reflection +- python bindings + +## Getting Started + +### Cmake build + +If `nix` is available, you probably prefer the nix build. +Otherwise continue reading.. + +The cmake build has two phases, because it needs to bootstrap +generated `xo-cmake-config`, `xo-build` helpers. + +``` +$ cd xo +$ PREFIX=/path/to/say/usr/local +# phase 1 +$ cmake -B .build0 -S xo-cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} +$ cmake --build .build0 +$ cmake --install .build0 +# phase 2 +$ cmake -B .build -S . -DCMAKE_INSTALL_PREFIX=${PREFIX} +$ cmake --build .build +$ cmake --install .build +``` + +### Nix Build + +Nix build uses toplevel `default.nix`, +along with top-level `pkgs/xo-foo.nix` for each subproject `foo`. +It doesn't interact with toplevel `CMakeLists.txt`. + +``` +$ nix-build -A xo-userenv +``` + +This builds all xo subprojects, assembles sandbox under `./result`. + +``` +$ tree -L 1 ./result +./result +├── bin +│   ├── xo-build +│   ├── xo-cmake-config +│   └── xo-cmake-lcov-harness +└── share + ├── cmake + │   └── xo_macros + │   ├── code-coverage.cmake + │   ├── xo-project-macros.cmake + │   └── xo_cxx.cmake + ├── etc + │   └── xo + │   └── subsystem-list + └── xo-macros + ├── Doxyfile.in + ├── gen-ccov.in + └── xo-bootstrap-macros.cmake +``` + +## To add a new satellite repo + +1. check clone in clean state (all local changes committed or unwound) + +2. add satellite as remote + +``` +$ git remote add xo-foo git@github.com:Rconybea/xo-foo.git +$ git fetch xo-foo +``` + +3. checkout satellite repo + +``` +$ git subtree add --prefix=xo-foo main +``` diff --git a/xo-indentlog/default.nix b/xo-indentlog/default.nix new file mode 100644 index 00000000..5b619074 --- /dev/null +++ b/xo-indentlog/default.nix @@ -0,0 +1,94 @@ +#{ pkgs ? import { overlays = [ (final: prev: { llvmPackages = prev.llvmPackages_17; }) ]; } }: +#pkgs.mkShell { +# buildInputs = [ pkgs.coreutils ]; +#} + + +{ + nixpkgs-path ? ../nixpkgs, + +# pkgs ? import (fetchTarball { +# # 24.05-darwin works on macos, clang17, llvm 18 (copying from xo-nix2) +# url = "https://github.com/NixOS/nixpkgs/archive/dd868b7bd4d1407d607da0d1d9c5eca89132e2f7.tar.gz"; +# }), +} : + +let + # this approach (overlays) is effective, but has super wide cross-section, + # since absolutely everything has to be rebuilt from source + # + + llvm-overlay = self: super: { + # use 'super' when you want to override the terms of a package. + # use 'self' when pointing to an existing package + + llvmPackages = super.llvmPackages_18; + }; + + xo-overlay = self: super: + let + # Choose the LLVM version you want + llvmPackages = self.llvmPackages_18; + in + + let + # Rebuild stdenv to use that LLVM version + customStdenv = super.overrideCC super.stdenv llvmPackages.clang; + in + + { + xo-cmake = self.callPackage pkgs/xo-cmake.nix {}; + xo-indentlog = self.callPackage pkgs/xo-indentlog.nix {}; +# xo-refcnt = self.callPackage pkgs/xo-refcnt.nix {}; +# xo-subsys = self.callPackage pkgs/xo-subsys.nix {}; +# xo-randomgen = self.callPackage pkgs/xo-randomgen.nix {}; +# xo-ordinaltree = self.callPackage pkgs/xo-ordinaltree.nix {}; +# xo-pyutil = self.callPackage pkgs/xo-pyutil.nix {}; +# xo-flatstring = self.callPackage pkgs/xo-flatstring.nix {}; +# xo-reflectutil = self.callPackage pkgs/xo-reflectutil.nix {}; +# xo-reflect = self.callPackage pkgs/xo-reflect.nix {}; +# xo-pyreflect = self.callPackage pkgs/xo-pyreflect.nix {}; +# xo-ratio = self.callPackage pkgs/xo-ratio.nix {}; +# xo-unit = self.callPackage pkgs/xo-unit.nix {}; +# xo-pyunit = self.callPackage pkgs/xo-pyunit.nix {}; +# xo-expression = self.callPackage pkgs/xo-expression.nix {}; +# xo-pyexpression = self.callPackage pkgs/xo-pyexpression.nix {}; +# xo-tokenizer = self.callPackage pkgs/xo-tokenizer.nix {}; +# xo-reader = self.callPackage pkgs/xo-reader.nix {}; +# xo-jit = self.callPackage pkgs/xo-jit.nix { stdenv = customStdenv; +# clang = llvmPackages.clang; +# llvm = llvmPackages.llvm; }; +# xo-pyjit = self.callPackage pkgs/xo-pyjit.nix {}; +# xo-callback = self.callPackage pkgs/xo-callback.nix {}; +# xo-webutil = self.callPackage pkgs/xo-webutil.nix {}; +# xo-pywebutil = self.callPackage pkgs/xo-pywebutil.nix {}; +# xo-printjson = self.callPackage pkgs/xo-printjson.nix {}; +# xo-pyprintjson = self.callPackage pkgs/xo-pyprintjson.nix {}; +# xo-reactor = self.callPackage pkgs/xo-reactor.nix {}; +# xo-pyreactor = self.callPackage pkgs/xo-pyreactor.nix {}; +# xo-websock = self.callPackage pkgs/xo-websock.nix {}; +# xo-pywebsock = self.callPackage pkgs/xo-pywebsock.nix {}; +# xo-statistics = self.callPackage pkgs/xo-statistics.nix {}; +# xo-distribution = self.callPackage pkgs/xo-distribution.nix {}; +# xo-pydistribution = self.callPackage pkgs/xo-pydistribution.nix {}; +# xo-simulator = self.callPackage pkgs/xo-simulator.nix {}; +# xo-pysimulator = self.callPackage pkgs/xo-pysimulator.nix {}; +# xo-process = self.callPackage pkgs/xo-process.nix {}; +# xo-pyprocess = self.callPackage pkgs/xo-pyprocess.nix {}; +# xo-kalmanfilter = self.callPackage pkgs/xo-kalmanfilter.nix {}; +# xo-pykalmanfilter = self.callPackage pkgs/xo-pykalmanfilter.nix {}; +# + xo-userenv = self.callPackage pkgs/xo-userenv.nix {}; +# xo-userenv-slow = self.callPackage pkgs/xo-userenv-slow.nix {}; + }; + +in +let + pkgs = import nixpkgs-path { + overlays = [ +# llvm-overlay + xo-overlay + ]; + }; +in +pkgs diff --git a/xo-indentlog/pkgs/xo-cmake.nix b/xo-indentlog/pkgs/xo-cmake.nix new file mode 100644 index 00000000..69c32bab --- /dev/null +++ b/xo-indentlog/pkgs/xo-cmake.nix @@ -0,0 +1,15 @@ +{ + # dependencies + + stdenv, + cmake +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-cmake"; + + src = ../xo-cmake; + + nativeBuildInputs = [ cmake ]; + }) diff --git a/xo-indentlog/pkgs/xo-indentlog.nix b/xo-indentlog/pkgs/xo-indentlog.nix new file mode 100644 index 00000000..12255020 --- /dev/null +++ b/xo-indentlog/pkgs/xo-indentlog.nix @@ -0,0 +1,18 @@ +{ + # dependencies + stdenv, cmake, catch2, + + xo-cmake, +} : + +stdenv.mkDerivation (finalattrs: + { + name = "xo-indentlog"; + version = "1.0"; + + src = ../xo-indentlog; + + cmakeFlags = ["-DCMAKE_MODULE_PATH=${xo-cmake}/share/cmake"]; + doCheck = true; + nativeBuildInputs = [ cmake catch2 xo-cmake ]; + }) diff --git a/xo-indentlog/pkgs/xo-userenv.nix b/xo-indentlog/pkgs/xo-userenv.nix new file mode 100644 index 00000000..b70bb3f6 --- /dev/null +++ b/xo-indentlog/pkgs/xo-userenv.nix @@ -0,0 +1,99 @@ +# builds environment with all xo packages, +# using combined output directory for each. +# +# parallels github actions build on stock ubuntu, +# except that we use nixpkgs for toolchain +# +# For xo development, probably prefer xo-userenv.nix instead of this xo-userenv-slow.nix: +# 1. xo-userenv.nix allows parallel build +# 2. xo-userenv.nix only rebuilds xo packages that have changed + +{ + # nixpkgs dependencies + buildEnv, + stdenv, + cmake, + catch2, + eigen, + libwebsockets, + jsoncpp, + doxygen, + sphinx, + python3Packages, + + # xo dependencies + xo-cmake, + xo-indentlog, +# xo-subsys, +# xo-refcnt, +# xo-randomgen, +# xo-ordinaltree, +# xo-flatstring, +# xo-reflectutil, +# xo-ratio, +# xo-unit, +# xo-pyunit, +# xo-pyutil, +# xo-reflect, +# xo-pyreflect, +# xo-printjson, +# xo-pyprintjson, +# xo-callback, +# xo-webutil, +# xo-pywebutil, +# xo-reactor, +# xo-pyreactor, +# xo-simulator, +# xo-pysimulator, xo-distribution, xo-pydistribution, xo-process, xo-pyprocess, xo-statistics, xo-kalmanfilter, +# xo-pykalmanfilter, xo-websock, xo-pywebsock, xo-tokenizer, +# xo-expression, xo-pyexpression, xo-reader, +# xo-jit, +# xo-pyjit + + # other args + + # someconfigurationoption ? false +} : + +buildEnv { + name = "xo-userenv"; + paths = [ xo-cmake + xo-indentlog +# xo-refcnt +# xo-subsys +# xo-randomgen +# xo-ordinaltree +# xo-pyutil +# xo-flatstring +# xo-reflectutil +# xo-reflect +# xo-pyreflect +# xo-ratio +# xo-unit +# xo-pyunit +# xo-expression +# xo-pyexpression +# xo-tokenizer +# xo-reader +# xo-jit +# xo-pyjit +# xo-callback +# xo-webutil +# xo-pywebutil +# xo-printjson +# xo-pyprintjson +# xo-reactor +# xo-pyreactor +# xo-websock +# xo-pywebsock +# xo-statistics +# xo-distribution +# xo-pydistribution +# xo-simulator +# xo-pysimulator +# xo-process +# xo-pyprocess +# xo-kalmanfilter +# xo-pykalmanfilter + ]; +} diff --git a/xo-indentlog/xo-cmake/.github/workflows/xo-cpp-main.yml b/xo-indentlog/xo-cmake/.github/workflows/xo-cpp-main.yml new file mode 100644 index 00000000..a792ec7d --- /dev/null +++ b/xo-indentlog/xo-cmake/.github/workflows/xo-cpp-main.yml @@ -0,0 +1,74 @@ +name: XO cmake xo-cpp-builder + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + container: + # custom docker image. see github.com:rconybea/docker-xo-builder for definition + image: ghcr.io/rconybea/docker-xo-builder:v1 + + steps: + - name: xo-cmake + run: | + # treat github.com as known host to prevent shtoopid SSL errors + mkdir -p ~/.ssh + ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + cat ~/.ssh/known_hosts + + git config --global http.sslVerify false + + XO_NAME=xo-cmake + XO_SRC=repo/${XO_NAME} + XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME} + PREFIX=${{github.workspace}}/local + + XO_REPO=https://github.com/rconybea/xo-cmake.git + + mkdir -p ${XO_SRC} + mkdir -p ${XO_BUILDDIR} + + echo "::group::clone ${XO_NAME}" + export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' + export GIT_SSL_NOVERIFY=true + git clone ${XO_REPO} ${XO_SRC} + echo "::endgroup" + + echo "::group::configure ${XO_NAME}" + cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC} + echo "::endgroup" + + echo "::group::compile ${XO_NAME}" + cmake --build ${XO_BUILDDIR} -j + echo "::endgroup" + + echo "::group::local install ${XO_NAME}" + cmake --install ${XO_BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree ${PREFIX} + echo "::endgroup" + + echo "::group::verify" + ls -l ${PREFIX}/bin/xo-cmake-config + ${PREFIX}/bin/xo-cmake-config --help + echo "$(${PREFIX}/bin/xo-cmake-config --lcov-exe)" + echo "$(${PREFIX}/bin/xo-cmake-config --genhtml-exe)" + echo "$(${PREFIX}/bin/xo-cmake-config --lcov-harness-exe)" + echo "$(${PREFIX}/bin/xo-cmake-config --gen-ccov-template)" + echo "$(${PREFIX}/bin/xo-cmake-config --cmake-module-path)" + echo "::endgroup" diff --git a/xo-indentlog/xo-cmake/.gitignore b/xo-indentlog/xo-cmake/.gitignore new file mode 100644 index 00000000..04e2da49 --- /dev/null +++ b/xo-indentlog/xo-cmake/.gitignore @@ -0,0 +1,3 @@ +.projectile +.build +compile_commands.json diff --git a/xo-indentlog/xo-cmake/CMakeLists.txt b/xo-indentlog/xo-cmake/CMakeLists.txt new file mode 100644 index 00000000..14226afa --- /dev/null +++ b/xo-indentlog/xo-cmake/CMakeLists.txt @@ -0,0 +1,67 @@ +cmake_minimum_required(VERSION 3.10) + +project(xo_macros VERSION 1.0) + +# if any are useful for this project.. +include (GNUInstallDirs) + +set(XO_PROJECT_NAME xo_macros) + +# LCOV_EXECUTABLE,GENHTML_EXECUTABLE: needed by xo-cmake-lcov-harness.in +find_program(LCOV_EXECUTABLE NAMES lcov) +find_program(GENHTML_EXECUTABLE NAMES genhtml) + +configure_file( + ${PROJECT_SOURCE_DIR}/bin/xo-cmake-lcov-harness.in + ${PROJECT_BINARY_DIR}/xo-cmake-lcov-harness + @ONLY + ) + +configure_file( + ${PROJECT_SOURCE_DIR}/bin/xo-cmake-config.in + ${PROJECT_BINARY_DIR}/xo-cmake-config + @ONLY + ) + +configure_file( + ${PROJECT_SOURCE_DIR}/bin/xo-build.in + ${PROJECT_BINARY_DIR}/xo-build + @ONLY + ) + +install( + FILES + "cmake/xo_macros/xo-project-macros.cmake" + "cmake/xo_macros/xo_cxx.cmake" + "cmake/xo_macros/code-coverage.cmake" + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_DATADIR}/cmake/xo_macros +) + +install( + FILES + "${PROJECT_BINARY_DIR}/xo-cmake-lcov-harness" + "${PROJECT_BINARY_DIR}/xo-cmake-config" + "${PROJECT_BINARY_DIR}/xo-build" + PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE + DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +install( + FILES + "etc/xo/subsystem-list" + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_DATADIR}/etc/xo +) + +# The cmake template gen-ccov.in should be expanded in downstream project; +# to pickup downstream project's PROJECT_SOURCE_DIR / PROJECT_BINARY_DIR +# +install( + FILES + "share/xo-macros/gen-ccov.in" + "share/xo-macros/Doxyfile.in" + "share/xo-macros/xo-bootstrap-macros.cmake" + PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE + DESTINATION ${CMAKE_INSTALL_DATADIR}/xo-macros +) diff --git a/xo-indentlog/xo-cmake/FAQ b/xo-indentlog/xo-cmake/FAQ new file mode 100644 index 00000000..e30a679f --- /dev/null +++ b/xo-indentlog/xo-cmake/FAQ @@ -0,0 +1,120 @@ +# Unit test build can't find library: + +Missing dependency at link time + +``` +set(SELF_EXE mumble) +set(SELF_SRCS mumble.cpp) + +xo_add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_self_dependency(${SELF_EXE} xo_foo) +``` + +and build fails with `cannot find -lxo_bar`. + +Possible causes: + +1. missing cmake export for a dependency of `xo_foo`. + Check `xo_foo/cmake/xo_fooConfig.cmake.in`: + + If `xo_foo` depends on `xo_bar`, then cmake export + needs to have `find_dependency(xo_bar)` + +``` +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(xo_bar) +find_dependency(xo_maybemoar) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") +``` + +# Howto introduce sphinx documentation to an XO project + +REMINDER: must re-run `cmake` after introducing skeleton, since build copies files from docs/ to .build directory + +Minimal skeleton for docs/ directory: + +``` +xo-foo ++- docs + +- CMakeLists.txt + +- conf.py + +- _static + +- index.rst +``` + +CMakeLists.txt: +``` +# xo-foo/docs/CMakeLists.txt + +xo_doxygen_collect_deps() +xo_docdir_doxygen_config() +xo_docdir_sphinx_config( + index.rst +) +``` + +conf.py: +``` +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'xo jit documentation' +copyright = '2024, Roland Conybeare' +author = 'Roland Conybeare' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +#extensions = [] +extensions = [ "breathe", + "sphinx.ext.mathjax", # inline math + "sphinx.ext.autodoc", # generate info from docstrings + "sphinxcontrib.ditaa", # diagrams-through-ascii-art + "sphinxcontrib.plantuml" # text -> uml diagrams + ] + +# note: breathe requires doxygen xml output -> must have GENERATE_XML = YES in Doxyfile.in +# match project name in Doxyfile.in +breathe_default_project = "xodoxxml" + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +pygments_style = 'sphinx' + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +#html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] +html_favicon = '_static/img/favicon.ico' +``` + +index.rst: +``` +xo-foo documentation +==================== + +text here + +.. toctree:: + :maxdepth: 2 + :caption: xo-foo contents + + genindex + search +``` + +_static: + +copy from xo-unit/docs/_static. Just need .ico diff --git a/xo-indentlog/xo-cmake/README.md b/xo-indentlog/xo-cmake/README.md new file mode 100644 index 00000000..8ccf76bb --- /dev/null +++ b/xo-indentlog/xo-cmake/README.md @@ -0,0 +1,61 @@ +# XO cmake modules + +Collects cmake macros to be shared across XO projects (e.g. indentlog, reflect, kalman, ..) + +## Features + +- support for both manyrepo and monorepo projects +- support for generating cmake `xxxConfig.cmake` files, so cmake `find_package()` works reliably +- support for header-only libraries +- support for pybind11 libraries +- documentation generation using doxygen + breathe + sphinx +- code coverage using ccov + lcov + +## Getting Started + +### copy repo + +``` +$ git clone https://github.com:rconybea/xo-cmake.git +``` + +### configure + install +``` +$ cd xo-cmake +$ cmake -DCMAKE_INSTALL_PREFIX=/usr/local -B .build -S . # ..or desired prefix +$ cmake --install .build +``` + +## use from a cmake project + +In some project `foo`: +``` +$ cd foo +$ mkdir cmake +$ cp $PREFIX/share/xo-macros/xo-bootstrap-macros.cmake cmake/ +``` + +`xo-bootstrap-macros-cmake` has two vital jobs: +1. set `XO_CMAKE_CONFIG_EXECUTABLE` (locate `xo-cmake-config`) +2. set `CMAKE_MODULE_PATH` (obtained from `xo-cmake-config --cmake-module-path`) + +then in `foo/CMakeLists.txt`: +``` +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() +``` + +Now as long as `$PREFIX/bin` is in `PATH`: +``` +$ cd mybuild +$ cmake path/to/foo/source +``` + +### or set `XO_CMAKE_CONFIG_EXECUTABLE` and `CMAKE_MODULE_PATH` + +In some project `foo`: +``` +$ cd mybuild +$ cmake -DXO_CMAKE_CONFIG_EXECUTABLE=xo-cmake-config -DCMAKE_MODULE_PATH=$(xo-cmake-config --cmake-module-path) path/to/foo/source +``` diff --git a/xo-indentlog/xo-cmake/bin/xo-build.in b/xo-indentlog/xo-cmake/bin/xo-build.in new file mode 100644 index 00000000..194ce3df --- /dev/null +++ b/xo-indentlog/xo-cmake/bin/xo-build.in @@ -0,0 +1,220 @@ +#!/usr/bin/env bash + +usage() { + cat < 0 ]]; do + case "$1" in + -u | --usage) + cmd='usage' + ;; + -h | --help) + cmd='help' + ;; + -n) + noop_flag=1 + ;; + -S) + shift + pathtosource=$1 + ;; + -S=*) + pathtosource="${1#*=}" + ;; + -B) + shift + pathtobuild=$1 + ;; + -B=*) + pathtobuild="${1#*=}" + ;; + --list) + cmd='list' + ;; + --xoname=*) + xoname="${1#*=}" + ;; + --repo) + repo_flag=1 + ;; + --clone) + clone_flag=1 + ;; + --configure|--config) + configure_flag=1 + ;; + --build) + build_flag=1 + ;; + --install) + install_flag=1 + ;; + xo-*) + xoname="$1" + ;; + *) + usage + exit 1 + ;; + esac + + shift +done + +echo xoname=$xoname pathtosource=$pathtosource pathtobuild=$pathtobuild + +if [[ -z "$pathtosource" ]]; then + pathtosource=$xoname +fi + +if [[ -z "$pathtobuild" ]]; then + pathtobuild=$xoname/.build +fi + +SUBSYSTEMLIST_FILE=@CMAKE_INSTALL_FULL_DATADIR@/etc/xo/subsystem-list + +subsystem_list() { + cat ${SUBSYSTEMLIST_FILE} +} + +XO_REPO_STEM="https://github.com/Rconybea" + +repo() { + xoname=$1 + + case "$xoname" in + xo-indentlog) + echo "${XO_REPO_STEM}/indentlog.git" + ;; + xo-refcnt) + echo "${XO_REPO_STEM}/refcnt.git" + ;; + xo-subsys) + echo "${XO_REPO_STEM}/subsys.git" + ;; + xo-reflect) + echo "${XO_REPO_STEM}/reflect.git" + ;; + *) + if grep -q $1 ${SUBSYSTEMLIST_FILE}; then + echo "${XO_REPO_STEM}/${1}.git" + else + >&2 echo "$0: unknown xo component [${xoname}]" + return 1 + fi + esac + + return 0 +} + +if [[ $cmd == 'usage' ]]; then + echo -n "usage: " + usage + exit 0 +elif [[ $cmd == 'help' ]]; then + echo -n "help; " + help + exit 0 +fi + +set -e + +if [[ $cmd == 'list' ]]; then + subsystem_list +fi + +if [[ $repo_flag -eq 1 ]]; then + if [[ -n "$xoname" ]]; then + repo $xoname + fi +fi + +if [[ $clone_flag -eq 1 ]]; then + if [[ -n "$xoname" ]]; then + url=$(repo $xoname) + + cmd="git clone $url" + + if [[ $noop_flag -eq 1 ]]; then + echo $cmd + else + $cmd + fi + fi +fi + +if [[ $configure_flag -eq 1 ]]; then + if [[ -n "$xoname" ]]; then + cmd="cmake -DCMAKE_INSTALL_PREFIX=@CMAKE_INSTALL_PREFIX@ -S $pathtosource -B $pathtobuild" + + if [[ $noop_flag -eq 1 ]]; then + echo $cmd + else + $cmd + fi + fi +fi + +if [[ $build_flag -eq 1 ]]; then + if [[ -n "$xoname" ]]; then + cmd="cmake --build $pathtobuild -j" + + if [[ $noop_flag -eq 1 ]]; then + echo $cmd + else + $cmd + fi + fi +fi + +if [[ $install_flag -eq 1 ]]; then + if [[ -n "$xoname" ]]; then + cmd="cmake --install $pathtobuild" + + if [[ $noop_flag -eq 1 ]]; then + echo $cmd + else + $cmd + fi + fi +fi diff --git a/xo-indentlog/xo-cmake/bin/xo-cmake-config.in b/xo-indentlog/xo-cmake/bin/xo-cmake-config.in new file mode 100755 index 00000000..bbea1361 --- /dev/null +++ b/xo-indentlog/xo-cmake/bin/xo-cmake-config.in @@ -0,0 +1,86 @@ +#!/usr/bin/env bash + +usage() { + echo "$0 [-u|--usage|-h|--help|--lcov-exe|--genhtml-exe|--lcov-harness-exe|--gen-ccov-template|--cmake-module-path|--subsystem-list]" 1>&2 +} + +help() { + usage + + cat < 0 ]]; do + case "$1" in + -u | --usage) + cmd='usage' + ;; + -h | --help) + cmd='help' + ;; + --cmake-module-path) + cmd='cmake_module_path' + ;; + --lcov-exe) + cmd='lcov_exe' + ;; + --genhtml-exe) + cmd='genhtml_exe' + ;; + --lcov-harness-exe) + cmd='lcov_harness_exe' + ;; + --gen-ccov-template) + cmd='gen_ccov_template' + ;; + --doxygen-template) + cmd='doxygen_template' + ;; + --subsystem-list) + cmd='subsystem_list' + ;; + *) + usage + exit 1 + ;; + esac + + shift +done + +if [[ $cmd == 'usage' ]]; then + echo -n "usage: " + usage +elif [[ $cmd == 'help' ]]; then + echo -n "help: " + help +elif [[ $cmd == 'cmake_module_path' ]]; then + echo -n @CMAKE_INSTALL_FULL_DATADIR@/cmake +elif [[ $cmd == 'lcov_exe' ]]; then + echo -n @LCOV_EXECUTABLE@ +elif [[ $cmd == 'genhtml_exe' ]]; then + echo -n @GENHTML_EXECUTABLE@ +elif [[ $cmd == 'lcov_harness_exe' ]]; then + echo -n @CMAKE_INSTALL_FULL_BINDIR@/xo-cmake-lcov-harness +elif [[ $cmd == 'gen_ccov_template' ]]; then + echo -n @CMAKE_INSTALL_FULL_DATADIR@/xo-macros/gen-ccov.in +elif [[ $cmd == 'doxygen_template' ]]; then + echo -n @CMAKE_INSTALL_FULL_DATADIR@/xo-macros/Doxyfile.in +elif [[ $cmd == 'subsystem_list' ]]; then + echo -n @CMAKE_INSTALL_FULL_DATADIR@/etc/xo/subsystem-list +fi diff --git a/xo-indentlog/xo-cmake/bin/xo-cmake-lcov-harness.in b/xo-indentlog/xo-cmake/bin/xo-cmake-lcov-harness.in new file mode 100755 index 00000000..414b950a --- /dev/null +++ b/xo-indentlog/xo-cmake/bin/xo-cmake-lcov-harness.in @@ -0,0 +1,121 @@ +#!/usr/bin/env bash + +srcdir=$1 +builddir=$2 +outputstem=$3 # optional +lcov=$4 # optional +genhtml=$5 # optional + +if [[ -z "${srcdir}" ]]; then + echo "xo-cmake-lcov-harness: expected non-empty srcdir" + exit 1 +fi + +if [[ -z "${builddir}" ]]; then + echo "xo-cmake-lcov-harness: expected non-empty builddir" + exit 1 +fi + +if [[ -z ${outputstem} ]]; then + outputstem=$builddir/ccov/out +fi + +if [[ -z ${lcov} ]]; then + lcov=@LCOV_EXECUTABLE@ + if [[ $lcov == "LCOV_EXECUTABLE-NOTFOUND" ]]; then + echo "xo-cmake-lcov-harness: lcov executable not found during xo-cmake build/install" + exit 1 + fi +fi + +if [[ -z ${genhtml} ]]; then + genhtml=@GENHTML_EXECUTABLE@ + if [[ $genhtml == "GENHTML_EXECUTABLE-NOTFOUND" ]]; then + echo "xo-cmake-lcov-harness: genhtml executable not found during xo-cmake build/install" + exit 1 + fi +fi + +mkdir -p $builddir/ccov + +# directory stems for location of {.gcda, gcno} coverage information, +# +# if we have source tree: +# +# ${srcdir} +# +- foo +# | \- foo.cpp +# \- bar +# \- quux +# +- quux.cpp +# \- quux_main.cpp +# +# then we expect build tree: +# +# ${builddir} +# +- foo +# | \- CMakeFiles +# | \- foo_target.dir +# | +- foo.cpp.gcda +# | \- foo.cpp.gcno +# +- bar +# \- quux +# \- CMakeFiles +# \- target4quux.dir +# +- quux.cpp.gcda +# +- quux.cpp.gcno +# +- quux_main.cpp.gcda +# \- quux_main.cpp.gcno +# +# in which case will have cmd_body: +# +# ${primarydirs} +# ./foo/CMakeFiles/foo_target.dir +# ./bar/quux/CMakeFiles/target4quux.dir +# +# here foo_target, quux_target are whatever build is using for corresponding cmake target names. +# +# We want to invoke lcov like: +# +# lcov --capture \ +# --output ${builddir}/ccov \ +# --exclude /utest/ \ +# --base-directory ${srcdir}/foo --directory ${builddir}/foo/CMakeFiles/foo_target.dir \ +# --base-directory ${srcdir}/bar/quux --directory ${builddir}/bar/quux/CMakeFiles/target4quux.dir +# +primarydirs=$(cd ${builddir} && find -name '*.gcno' \ + | xargs --replace=xx dirname xx \ + | uniq \ + | sed -e 's:^\./::') + +#echo "primarydirs=${primarydirs}" + +cmd="${lcov} --output ${outputstem}.info --capture --ignore-errors source" + +for bdir in ${primarydirs}; do + sdir=$(dirname $(dirname ${bdir})) + + cmd="${cmd} --base-directory ${srcdir}/${sdir} --directory ${builddir}/${bdir}" +done + +#echo cmd=${cmd} + +set -x + +# capture +${cmd} + +# keep only files with paths under source tree +# (don't want coverage for external libraries such as libstdc++ etc) +${lcov} --extract ${outputstem}.info "${srcdir}/*" --output ${outputstem}2.info + +# remove unit test dirs +# (we're interested in coverage of our installed code, not of the unit tests that exercise it) +${lcov} --remove ${outputstem}2.info '*/utest/*' --output ${outputstem}3.info + +# generate .html tree +mkdir -p ${builddir}/ccov/html +${genhtml} --ignore-errors source --show-details --prefix ${srcdir} --output-directory ${builddir}/ccov/html ${outputstem}3.info + +# also send report to stdout +${lcov} --list ${outputstem}3.info diff --git a/xo-indentlog/xo-cmake/cmake/xo_macros/code-coverage.cmake b/xo-indentlog/xo-cmake/cmake/xo_macros/code-coverage.cmake new file mode 100644 index 00000000..b6b36064 --- /dev/null +++ b/xo-indentlog/xo-cmake/cmake/xo_macros/code-coverage.cmake @@ -0,0 +1,678 @@ +# +# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# USAGE: To enable any code coverage instrumentation/targets, the single CMake +# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or +# on the command line. +# +# From this point, there are two primary methods for adding instrumentation to +# targets: +# +# 1 - A blanket instrumentation by calling `add_code_coverage()`, where +# all targets in that directory and all subdirectories are automatically +# instrumented. +# +# 2 - Per-target instrumentation by calling +# `target_code_coverage()`, where the target is given and thus only +# that target is instrumented. This applies to both libraries and executables. +# +# To add coverage targets, such as calling `make ccov` to generate the actual +# coverage information for perusal or consumption, call +# `target_code_coverage()` on an *executable* target. +# +# Example 1: All targets instrumented +# +# In this case, the coverage information reported will will be that of the +# `theLib` library target and `theExe` executable. +# +# 1a: Via global command +# +# ~~~ +# add_code_coverage() # Adds instrumentation to all targets +# +# add_library(theLib lib.cpp) +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target +# # (instrumentation already added via global anyways) +# # for generating code coverage reports. +# ~~~ +# +# 1b: Via target commands +# +# ~~~ +# add_library(theLib lib.cpp) +# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets. +# +# add_executable(theExe main.cpp) +# target_link_libraries(theExe PRIVATE theLib) +# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports. +# ~~~ +# +# Example 2: Target instrumented, but with regex pattern of files to be excluded +# from report +# +# ~~~ +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ +# +# Example 3: Target added to the 'ccov' and 'ccov-all' targets +# +# ~~~ +# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders. +# +# add_executable(theExe main.cpp non_covered.cpp) +# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder. +# ~~~ + +# Options +option( + CODE_COVERAGE + "Builds targets with code coverage instrumentation. (Requires GCC or Clang)" + OFF) + +# Programs +find_program(LLVM_COV_PATH llvm-cov) +find_program(LLVM_PROFDATA_PATH llvm-profdata) +find_program(LCOV_PATH lcov) +find_program(GENHTML_PATH genhtml) +# Hide behind the 'advanced' mode flag for GUI/ccmake +mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH) + +# Variables +set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov) +set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1) + +# Common initialization/checks +if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED) + set(CODE_COVERAGE_ADDED ON) + + # Common Targets + add_custom_target( + ccov-preprocessing + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY} + DEPENDS ccov-clean) + + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + # Messages + message(STATUS "Building with llvm Code Coverage Tools") + + if(NOT LLVM_COV_PATH) + message(FATAL_ERROR "llvm-cov not found! Aborting.") + else() + # Version number checking for 'EXCLUDE' compatibility + execute_process(COMMAND ${LLVM_COV_PATH} --version + OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION + ${LLVM_COV_VERSION_CALL_OUTPUT}) + + if(LLVM_COV_VERSION VERSION_LESS "7.0.0") + message( + WARNING + "target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0" + ) + endif() + endif() + + # Targets + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E remove -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + else() + add_custom_target( + ccov-clean + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND ${CMAKE_COMMAND} -E rm -f + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list) + endif() + + # Used to get the shared object file list before doing the main all- + # processing + add_custom_target( + ccov-libs + COMMAND ; + COMMENT "libs ready for coverage report.") + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + # Messages + message(STATUS "Building with lcov Code Coverage Tools") + + if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type) + if(NOT ${upper_build_type} STREQUAL "DEBUG") + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + else() + message( + WARNING + "Code coverage results with an optimized (non-Debug) build may be misleading" + ) + endif() + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Targets + add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory + ${CMAKE_BINARY_DIR} --zerocounters) + + else() + message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") + endif() +endif() + +# Adds code coverage instrumentation to a library, or instrumentation/targets +# for an executable target. +# ~~~ +# EXECUTABLE ADDED TARGETS: +# GCOV/LCOV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# +# LLVM-COV: +# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter. +# ccov-${TARGET_NAME} : Generates HTML code coverage report. +# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information. +# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file. +# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information. +# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report. +# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line. +# ccov-all-export : Exports the coverage report to a JSON file. +# +# Required: +# TARGET_NAME - Name of the target to generate code coverage for. +# Optional: +# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE. +# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE. +# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects) +# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets. +# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets. +# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory +# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`. +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.** +# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output +# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call +# ~~~ +function(target_code_coverage TARGET_NAME) + # Argument parsing + set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN) + set(single_value_keywords COVERAGE_TARGET_NAME) + set(multi_value_keywords EXCLUDE OBJECTS ARGS) + cmake_parse_arguments( + target_code_coverage "${options}" "${single_value_keywords}" + "${multi_value_keywords}" ${ARGN}) + + # Set the visibility of target functions to PUBLIC, INTERFACE or default to + # PRIVATE. + if(target_code_coverage_PUBLIC) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY PUBLIC) + elseif(target_code_coverage_INTERFACE) + set(TARGET_VISIBILITY INTERFACE) + set(TARGET_LINK_VISIBILITY INTERFACE) + elseif(target_code_coverage_PLAIN) + set(TARGET_VISIBILITY PUBLIC) + set(TARGET_LINK_VISIBILITY) + else() + set(TARGET_VISIBILITY PRIVATE) + set(TARGET_LINK_VISIBILITY PRIVATE) + endif() + + if(NOT target_code_coverage_COVERAGE_TARGET_NAME) + # If a specific name was given, use that instead. + set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME}) + endif() + + if(CODE_COVERAGE) + + # Add code coverage instrumentation to the target's linker command + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY} + -fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs + -ftest-coverage) + target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov) + endif() + + # Targets + get_target_property(target_type ${TARGET_NAME} TYPE) + + # Add shared library to processing for 'all' targets + if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" >> + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + if(NOT TARGET ccov-libs) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-libs + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # For executables add targets to run and produce output + if(target_type STREQUAL "EXECUTABLE") + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # If there are shared objects to also work with, generate the string to + # add them here + foreach(SO_TARGET ${target_code_coverage_OBJECTS}) + # Check to see if the target is a shared object + if(TARGET ${SO_TARGET}) + get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE) + if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY") + set(SO_OBJECTS ${SO_OBJECTS} -object=$) + endif() + endif() + endforeach() + + # Run the executable, generating raw profile data Make the run data + # available for further processing. Separated to allow Windows to run + # this target serially. + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${CMAKE_COMMAND} -E env + LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw + $ ${target_code_coverage_ARGS} + COMMAND + ${CMAKE_COMMAND} -E echo "-object=$" + ${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list + COMMAND + ${CMAKE_COMMAND} -E echo + "${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw" + >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list + JOB_POOL ccov_serial_pool + DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME}) + + # Merge the generated profile data so llvm-cov can process it + add_custom_target( + ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_PROFDATA_PATH} merge -sparse + ${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o + ${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Ignore regex only works on LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print out details of the coverage information to the command line + add_custom_target( + ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Print out a summary of the coverage information to the command line + add_custom_target( + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} report $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} export $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${LLVM_COV_PATH} show $ ${SO_OBJECTS} + -instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}) + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO + "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info" + ) + + # Run the executable, generating coverage information + add_custom_target( + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND $ ${target_code_coverage_ARGS} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + + # Generate exclusion string for use + foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + if(NOT ${target_code_coverage_EXTERNAL}) + set(EXTERNAL_OPTION --no-external) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + else() + add_custom_target( + ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters + COMMAND $ ${target_code_coverage_ARGS} + COMMAND + ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory + ${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file + ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ${TARGET_NAME}) + endif() + + # Generates HTML output of the coverage information for perusal + add_custom_target( + ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + COMMAND + ${GENHTML_PATH} -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME} + ${COVERAGE_INFO} + DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + + add_custom_command( + TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME} + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report." + ) + + # AUTO + if(target_code_coverage_AUTO) + if(NOT TARGET ccov) + add_custom_target(ccov) + endif() + add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME}) + + if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID + MATCHES "GNU") + if(NOT TARGET ccov-report) + add_custom_target(ccov-report) + endif() + add_dependencies( + ccov-report + ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + + # ALL + if(target_code_coverage_ALL) + if(NOT TARGET ccov-all-processing) + message( + FATAL_ERROR + "Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'." + ) + endif() + + add_dependencies(ccov-all-processing + ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}) + endif() + endif() + endif() +endfunction() + +# Adds code coverage instrumentation to all targets in the current directory and +# any subdirectories. To add coverage instrumentation to only specific targets, +# use `target_code_coverage`. +function(add_code_coverage) + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + add_compile_options(-fprofile-instr-generate -fcoverage-mapping) + add_link_options(-fprofile-instr-generate -fcoverage-mapping) + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + add_compile_options(-fprofile-arcs -ftest-coverage) + link_libraries(gcov) + endif() + endif() +endfunction() + +# Adds the 'ccov-all' type targets that calls all targets added via +# `target_code_coverage` with the `ALL` parameter, but merges all the coverage +# data from them into a single large report instead of the numerous smaller +# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for +# use with coverage dashboards (e.g. codecov.io, coveralls). +# ~~~ +# Optional: +# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! +# ~~~ +function(add_code_coverage_all_targets) + # Argument parsing + set(multi_value_keywords EXCLUDE) + cmake_parse_arguments(add_code_coverage_all_targets "" "" + "${multi_value_keywords}" ${ARGN}) + + if(CODE_COVERAGE) + if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang" + OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") + + # Merge the profile data for all of the run executables + if(WIN32) + add_custom_target( + ccov-all-processing + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe + merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -sparse $$FILELIST) + else() + add_custom_target( + ccov-all-processing + COMMAND + ${LLVM_PROFDATA_PATH} merge -o + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`) + endif() + + # Regex exclude only available for LLVM >= 7 + if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0") + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} + -ignore-filename-regex='${EXCLUDE_ITEM}') + endforeach() + endif() + + # Print summary of the code coverage information to the command line + if(WIN32) + add_custom_target( + ccov-all-report + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe + report $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all-report + COMMAND + ${LLVM_COV_PATH} report `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + # Export coverage information so continuous integration tools (e.g. + # Jenkins) can consume it + add_custom_target( + ccov-all-export + COMMAND + ${LLVM_COV_PATH} export `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -format="text" ${EXCLUDE_REGEX} > + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json + DEPENDS ccov-all-processing) + + # Generate HTML output of all added targets for perusal + if(WIN32) + add_custom_target( + ccov-all + COMMAND + powershell -Command $$FILELIST = Get-Content + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show + $$FILELIST + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + else() + add_custom_target( + ccov-all + COMMAND + ${LLVM_COV_PATH} show `cat + ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list` + -instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata + -show-line-counts-or-regions + -output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + -format="html" ${EXCLUDE_REGEX} + DEPENDS ccov-all-processing) + endif() + + elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES + "GNU") + set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info") + + # Nothing required for gcov + add_custom_target(ccov-all-processing COMMAND ;) + + # Exclusion regex string creation + set(EXCLUDE_REGEX) + foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE}) + set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO} + '${EXCLUDE_ITEM}') + endforeach() + + if(EXCLUDE_REGEX) + set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file + ${COVERAGE_INFO}) + else() + set(EXCLUDE_COMMAND ;) + endif() + + # Capture coverage data + if(${CMAKE_VERSION} VERSION_LESS "3.17.0") + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + else() + add_custom_target( + ccov-all-capture + COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO} + COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture + --output-file ${COVERAGE_INFO} + COMMAND ${EXCLUDE_COMMAND} + DEPENDS ccov-preprocessing ccov-all-processing) + endif() + + # Generates HTML output of all targets for perusal + add_custom_target( + ccov-all + COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged + ${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR} + DEPENDS ccov-all-capture) + + endif() + + add_custom_command( + TARGET ccov-all + POST_BUILD + COMMAND ; + COMMENT + "Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report." + ) + endif() +endfunction() diff --git a/xo-indentlog/xo-cmake/cmake/xo_macros/xo-project-macros.cmake b/xo-indentlog/xo-cmake/cmake/xo_macros/xo-project-macros.cmake new file mode 100644 index 00000000..a18c9b8d --- /dev/null +++ b/xo-indentlog/xo-cmake/cmake/xo_macros/xo-project-macros.cmake @@ -0,0 +1,2 @@ +include(xo_macros/xo_cxx) +include(xo_macros/code-coverage) diff --git a/xo-indentlog/xo-cmake/cmake/xo_macros/xo_cxx.cmake b/xo-indentlog/xo-cmake/cmake/xo_macros/xo_cxx.cmake new file mode 100644 index 00000000..a5c0e3c3 --- /dev/null +++ b/xo-indentlog/xo-cmake/cmake/xo_macros/xo_cxx.cmake @@ -0,0 +1,1426 @@ +option(XO_ENABLE_EXAMPLES "enable building example programs" OFF) + +macro(xo_cxx_config_message) + message(STATUS "GUESSED_CMAKE_CMD=cmake -DXO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE} -DENABLE_TESTING=${ENABLE_TESTING} -DXO_ENABLE_EXAMPLES=${XO_ENABLE_EXAMPLES} -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} -DCMAKE_MODULE_PATH=${CMAKE_MODULE_PATH} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_DOCDIR=${CMAKE_INSTALL_DOCDIR} -B ${CMAKE_BINARY_DIR}") + message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + message(STATUS "CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") +endmacro() + +macro(xo_cxx_bootstrap_message) + if (NOT XO_SUBMODULE_BUILD) + xo_cxx_config_message() + endif() +endmacro() + +# deprecated -- prefer xo_cxx_toplevel_options2() +macro(xo_cxx_toplevel_options) + message(WARNING "deprecated: prefer xo_cxx_toplevel_options2") + + message("xo_cxx_toplevel_options: PROJECT=${PROJECT_NAME}") + + enable_language(CXX) + xo_toplevel_compile_options() + xo_toplevel_testing_options() + + add_custom_target(all_executables_${PROJECT_NAME}) + set_property( + TARGET all_executables_${PROJECT_NAME} + PROPERTY targets "") + + add_custom_target(all_libraries_${PROJECT_NAME}) + set_property( + TARGET all_libraries_${PROJECT_NAME} + PROPERTY targets "") +endmacro() + +# deprecated -- prefer xo_cxx_toplevel_options3() +macro(xo_cxx_toplevel_options2) + if (NOT DEFINED _xo_cxx_toplevel_done) + message("xo_cxx_toplevel_options2: PROJECT=${PROJECT_NAME}") + + enable_language(CXX) + xo_toplevel_compile_options() + enable_testing() + + add_custom_target(all_executables_${PROJECT_NAME}) + set_property( + TARGET all_executables_${PROJECT_NAME} + PROPERTY targets "") + + add_custom_target(all_libraries_${PROJECT_NAME}) + set_property( + TARGET all_libraries_${PROJECT_NAME} + PROPERTY targets "") + + add_custom_target(all_utest_executables_${PROJECT_NAME}) + set_property( + TARGET all_utest_executables_${PROJECT_NAME} + PROPERTY targets "") + endif() +endmacro() + +macro(xo_cxx_toplevel_options3) + xo_cxx_toplevel_options2() + xo_toplevel_config2() +endmacro() + +macro(xo_toplevel_testing_options) + enable_testing() + add_code_coverage() + add_code_coverage_all_targets(EXCLUDE /nix/store* utest/*) + + add_custom_target(all_utest_executables_${PROJECT_NAME}) + set_property( + TARGET all_utest_executables_${PROJECT_NAME} + PROPERTY targets "") +endmacro() + +# default build (cmake -DCMAKE_BUILD_TYPE= path/to/source) +# +macro(xo_toplevel_default_config2) + if ("${CMAKE_BUILD_TYPE}" STREQUAL "") + # clear out hardwired default. + # we want to override project-level defaults, + # but need to prevent interference from hardwired defaults + # (the problem with non-empty hardwired defaults is that we can't tell if they've + # been set on the command line) + # + set(CMAKE_CXX_FLAGS_DEFAULT "") + + # CMAKE_CXX_FLAGS_DEBUG is built-in to cmake and has non-empty default. + # -> we cannot tell whether it was set on the command line + # -> use PROJECT_CXX_FLAGS_DEBUG instead + # + # built-in default value is -g; can hardwire different project policy here + # + if (NOT DEFINED PROJECT_CXX_FLAGS_DEFAULT) + set(PROJECT_CXX_FLAGS_DEFAULT ${PROJECT_CXX_FLAGS} -fno-strict-aliasing -O + CACHE STRING "default c++ compiler flags") + endif() + + message(STATUS "PROJECT_CXX_FLAGS_DEFAULT: default c++ flags are [${PROJECT_CXX_FLAGS_DEFAULT}]") + + # note no $ selector here + add_compile_options("${PROJECT_CXX_FLAGS_DEFAULT}") + endif() +endmacro() + +# release build (cmake -DCMAKE_BUILD_TYPE=release path/to/source) +# +macro(xo_toplevel_release_config2) + if ("${CMAKE_BUILD_TYPE}" STREQUAL "release") + # clear out hardwired default + # we want to override project-level defaults, + # but need to prevent interference from hardwired defaults + # (the problem with non-empty hardwired defaults is that we can't tell if they've + # been set on the command line) + # + set(CMAKE_CXX_FLAGS_RELEASE "") + + # CMAKE_CXX_FLAGS_RELEASE is built-in to cmake and has non-empty default + # -> we cannot tell whether it was set on the command line + # -> use PROJECT_CXX_FLAGS_RELEASE instead + # + # built-in default value is -march=native -O3 -DNDEBUG + # + if (NOT DEFINED PROJECT_CXX_FLAGS_RELEASE) + set(PROJECT_CXX_FLAGS_RELEASE ${PROJECT_CXX_FLAGS} -fno-strict-aliasing -march=native -O3 -DNDEBUG + CACHE STRING "release c++ compiler flags") + endif() + + message(STATUS "PROJECT_CXX_FLAGS_RELEASE: release c++ flags are [${PROJECT_CXX_FLAGS_RELEASE}]") + + add_compile_options("$<$:${PROJECT_CXX_FLAGS_RELEASE}>") + endif() +endmacro() + +# debug build (cmake -DCMAKE_BUILD_TYPE=debug path/to/source) +# +macro(xo_toplevel_debug_config2) + if ("${CMAKE_BUILD_TYPE}" STREQUAL "debug") + # clear out hardwired default. + # we want to override project-level defaults, + # but need to prevent interference from hardwired defaults + # (the problem with non-empty hardwired defaults is that we can't tell if they've + # been set on the command line) + # + set(CMAKE_CXX_FLAGS_DEBUG "") + + # CMAKE_CXX_FLAGS_DEBUG is built-in to cmake and has non-empty default. + # -> we cannot tell whether it was set on the command line + # -> use PROJECT_CXX_FLAGS_DEBUG instead + # + # built-in default value is -g; can hardwire different project policy here + # + if (NOT DEFINED PROJECT_CXX_FLAGS_DEBUG) + set(PROJECT_CXX_FLAGS_DEBUG ${PROJECT_CXX_FLAGS} -fno-strict-aliasing -ggdb -Og + CACHE STRING "debug c++ compiler flags") + endif() + + message(STATUS "PROJECT_CXX_FLAGS_DEBUG: debug c++ flags are [${PROJECT_CXX_FLAGS_DEBUG}]") + + add_compile_options("$<$:${PROJECT_CXX_FLAGS_DEBUG}>") + endif() +endmacro() + + +# asan (address sanitizer) build (cmake -DCMAKE_BUILD_TYPE=asan path/to/source) +# +macro(xo_toplevel_asan_config2) + if ("${CMAKE_BUILD_TYPE}" STREQUAL "asan") + # following the same pattern used for xo_toplevel_debug_config2(), + # although not needed here (if PROJECT_CXX_FLAGS_ASAN has no builtin default value) + + set(CMAKE_CXX_FLAGS_ASAN "") + + if (NOT DEFINED PROJECT_CXX_FLAGS_ASAN) + set(PROJECT_CXX_FLAGS_ASAN ${PROJECT_CXX_FLAGS} -fno-strict-aliasing -Og -fsanitize=address + CACHE STRING "asan c++ compiler flags") + endif() + + message(STATUS "PROJECT_CXX_FLAGS_ASAN: asan c++ flags are [${PROJECT_CXX_FLAGS_ASAN}]") + + add_compile_options("$<$:${PROJECT_CXX_FLAGS_ASAN}>") + + if (NOT DEFINED PROJECT_LINK_FLAGS_ASAN) + set(PROJECT_LINK_FLAGS_ASAN ${PROJECT_LINK_FLAGS} -fsanitize=address + CACHE STRING "asan link flags") + endif() + message(STATUS "PROJECT_LINK_FLAGS_ASAN: asan link flags are [${PROJECT_LINK_FLAGS_ASAN}]") + + add_link_options("$<$:${PROJECT_LINK_FLAGS_ASAN}>") + endif() +endmacro() + +# support for +# cmake -DCMAKE_BUILD_TYPE= +# cmake -DCMAKE_BUILD_TYPE=release +# cmake -DCMAKE_BUILD_TYPE=debug +# cmake -DCMAKE_BUILD_TYPE=asan +# cmake -DCMAKE_BUILD_TYPE=coverage +# +macro(xo_toplevel_config2) + xo_toplevel_default_config2() + xo_toplevel_release_config2() + xo_toplevel_debug_config2() + xo_toplevel_asan_config2() + xo_toplevel_coverage_config2() +endmacro() + +# coverage build: +# 0. +# (cmake -DCMAKE_BUILD_TYPE=coverage path/to/source) +# 1. invoke instrumented executables for which you want coverage: +# (cmake --build path/to/build -- test) +# 2. post-process low-level coverage data +# (path/to/build/gen-ccov) +# 3. point browser to generated html data +# file:///path/to/build/ccov/html/index.html +# +macro(xo_toplevel_coverage_config2) + if ("${CMAKE_BUILD_TYPE}" STREQUAL "coverage") + #find_program(LCOV_EXECUTABLE NAMES lcov) + #find_program(GENHTML_EXECUTABLE NAMES genhtml) + # see bin/xo-cmake-lcov-harness in this repo + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --lcov-harness-exe OUTPUT_VARIABLE XO_CMAKE_LCOV_HARNESS_EXECUTABLE) + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --gen-ccov-template OUTPUT_VARIABLE XO_CMAKE_GEN_CCOV_TEMPLATE) + + if (NOT DEFINED PROJECT_CXX_FLAGS_COVERAGE) + # note: for clang would use -fprofile-instr-generate -fcoverage-mapping here instead and also at link time + set(PROJECT_CXX_FLAGS_COVERAGE -ggdb -Og -fprofile-arcs -ftest-coverage + CACHE STRING "coverage c++ compiler flags") + endif() + message(STATUS "PROJECT_CXX_FLAGS_COVERAGE: coverage c++ flags are [${PROJECT_CXX_FLAGS_COVERAGE}]") + + add_compile_options("$<$:${PROJECT_CXX_FLAGS_COVERAGE}>") + # when -DCMAKE_BUILD_TYPE=coverage, must also link executables with gcov + link_libraries("$<$:gcov>") + + if("${XO_CMAKE_GEN_CCOV_TEMPLATE}" STREQUAL "") + message(ERROR "xo_toplevel_testing_config2: XO_CMAKE_GEN_CCOV_TEMPLATE not set") + message(ERROR "see xo_toplevel_testing_options2()") + else() + message(DEBUG "XO_CMAKE_GEN_CCOV_TEMPLATE=${XO_CMAKE_GEN_CCOV_TEMPLATE}") + configure_file(${XO_CMAKE_GEN_CCOV_TEMPLATE} ${PROJECT_BINARY_DIR}/gen-ccov @ONLY) + file(CHMOD ${PROJECT_BINARY_DIR}/gen-ccov PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + endif() + endif() +endmacro() + +# target to build+install coverage report. +# +macro(xo_utest_coverage_config2) + if (XO_SUBMODULE_BUILD) + # in submodule build, will generate aggregate coverage report + # for all xo libraries. + else() + set(CCOV_OUTPUT_DIR ${PROJECT_BINARY_DIR}/ccov/html) + set(CCOV_INDEX_FILE ${CCOV_OUTPUT_DIR}/index.html) + # see xo_toplevel_coverage_report() + set(CCOV_REPORT_EXE ${PROJECT_BINARY_DIR}/gen-ccov) + # CMAKE_INSTALL_DOCDIR + # =default=> DATAROOTDIR/doc/PROJECT_NAME + # =default=> CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring + set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov) + + # collect utest deps (like xo_doxygen_collect_deps()) + get_target_property(_all_utests all_utest_executables_${PROJECT_NAME} targets) + message(DEBUG "_all_utests=${_all_utests}") + + # 'test' target should always be out-of-date + # + # DEPENDS: reminder - can't put 'test' here, requires 'all' target + # + add_custom_command( + OUTPUT ${CCOV_INDEX_FILE} + DEPENDS ${_all_utests} + COMMAND ${CCOV_REPORT_EXE} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Generating coverage report -> [${CCOV_OUTPUT_DIR}]") + + add_custom_target( + ccov + DEPENDS ${CCOV_INDEX_FILE} ${SELF_EXE}) + + # OPTIONAL: quietly skip this step if ccov report not generated + install( + DIRECTORY ${CCOV_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CCOV_INSTALL_DOCDIR} + COMPONENT Documentation + OPTIONAL) + endif() +endmacro() + +# caller should set ALL_LIBRARY_TARGETS ALL_UTEST_TARGETS before calling +# xo_doxygen_standard_deps(). +# +# xo_add_headeronly_library4(), xo_add_shared_library4(), xo_pybind11_library() +# all add their target to ALL_LIBRARY_TARGETS. +# +macro(xo_doxygen_collect_deps) + get_target_property(_all_exes all_executables_${PROJECT_NAME} targets) + get_target_property(_all_libs all_libraries_${PROJECT_NAME} targets) + get_target_property(_all_utests all_utest_executables_${PROJECT_NAME} targets) + + message(DEBUG "_all_exes=${_all_exes}") + message(DEBUG "_all_libs=${_all_libs}") + message(DEBUG "_all_utests=${_all_utests}") + + ## .hpp files reachable from xo-flatstring/include + ## + ## REMINDER: for reliability will need to re-run cmake when the set of .hpp files changes + ## + #file(GLOB_RECURSE DOX_HPP_FILES_GLOB ${PROJECT_SOURCE_DIR}/include "*.hpp") + #message(STATUS "DOX_HPP_FILES_GLOB=${DOX_HPP_FILES_GLOB}") + #set(DOX_DEPS ${_all_libs} ${_all_utests} ${DOX_HPP_FILES_GLOB}) + + set(DOX_DEPS ${_all_exes} ${_all_libs} ${_all_utests}) + + message(STATUS "DOX_DEPS=${DOX_DEPS}") +endmacro() + +# caller must set DOX_DEPS before invoking xo_toplevel_doxygen_config() +# +macro(xo_docdir_doxygen_config) + if (XO_SUBMODULE_BUILD) + # in submodule build, rely on toplevel docs/CMakeLists.txt file instead + else() + # look for doxygen executable + find_program(DOXYGEN_EXECUTABLE NAMES doxygen REQUIRED) + message(STATUS "DOXYGEN_EXECUTABLE=${DOXYGEN_EXECUTABLE}") + + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --doxygen-template OUTPUT_VARIABLE DOXYGEN_CONFIG_TEMPLATE) + message(STATUS "DOXYGEN_CONFIG_TEMPLATE=${DOXYGEN_CONFIG_TEMPLATE}") + + set(DOX_CONFIG_FILE ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + + set(DOX_INPUT_DIR ${PROJECT_SOURCE_DIR}) + set(DOX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/dox) + + set(DOX_INDEX_FILE ${DOX_OUTPUT_DIR}/html/index.html) + + configure_file( + ${DOXYGEN_CONFIG_TEMPLATE} ${DOX_CONFIG_FILE} + FILE_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE + @ONLY) + + file(MAKE_DIRECTORY ${DOX_OUTPUT_DIR}) + add_custom_command( + OUTPUT ${DOX_INDEX_FILE} + DEPENDS ${DOX_DEPS} + COMMAND "${DOXYGEN_EXECUTABLE}" ${DOX_CONFIG_FILE} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + MAIN_DEPENDENCY ${DOX_CONFIG_FILE} + COMMENT "Generating docs (doxygen)") + + # To build this target + # $ cmake --build .build -j -- doxygen + # or + # $ cd .build + # $ make doxygen + # + add_custom_target( + doxygen + DEPENDS ${DOX_INDEX_FILE} ${DOX_DEPS} + ) + + endif() +endmacro() + +macro(xo_docdir_sphinx_config rst_files) + list(APPEND SPHINX_RST_FILES ${rst_files}) + foreach(arg IN ITEMS ${ARGN}) + list(APPEND SPHINX_RST_FILES ${arg}) + endforeach() + + message(STATUS "SPHINX_RST_FILES=${SPHINX_RST_FILES}") + + if (XO_SUBMODULE_BUILD) + # in submodule build, rely on toplevel docs/CMakeLists.txt file instead + else() + # build docs starting from here only in standalone build. + # otherwise use top-level doxygen setup instead. + + #set(ALL_LIBRARY_TARGETS xo_flatstring) # todo: automate this from xo-cmake macros + #set(ALL_UTEST_TARGETS xo_flatstring_ex1 utest.flatstring) # todo: automate this from xo-cmake macros + + # look for sphinx-build executable + find_program(SPHINX_EXECUTABLE NAMES sphinx-build REQUIRED) + message(STATUS "SPHINX_EXECUTABLE=${SPHINX_EXECUTABLE}") + + set(SPHINX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/sphinx/html) + set(SPHINX_INDEX_FILE ${SPHINX_OUTPUT_DIR}/index.html) + + # root of sphinx doc tree + set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) + set(SPHINX_DEPS doxygen conf.py ${SPHINX_RST_FILES} ${SPHINX_RST_FILES_GLOB} ${DOX_DEPS}) + + add_custom_command( + OUTPUT ${SPHINX_INDEX_FILE} + DEPENDS ${SPHINX_DEPS} + COMMAND ${SPHINX_EXECUTABLE} + -b html -Dbreathe_projects.xodoxxml=${CMAKE_CURRENT_BINARY_DIR}/dox/xml + ${SPHINX_SOURCE} ${SPHINX_OUTPUT_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating docs (sphinx) -> [${SPHINX_OUTPUT_DIR}]") + + # make sphinx --> generate sphinx documentation + # + add_custom_target( + sphinx + DEPENDS ${SPHINX_INDEX_FILE}) + + # - html docs generated in build/docs/sphinx + # - copy the doc tree to share/doc/xo_unit/html + # + # DESTINATION: CMAKE_INSTALL_DOCDIR + # => DATAROOTDIR/doc/PROJECT_NAME + # => CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring + # OPTIONAL: install directory tree if it exists, + # but don't complain if it's missing + install( + DIRECTORY ${SPHINX_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_DOCDIR} + COMPONENT Documentation + OPTIONAL) + + # make docs --> generate sphinx documentation + add_custom_target( + docs + DEPENDS sphinx) + endif() + +endmacro() + +macro(xo_toplevel_compile_options) + define_property( + TARGET + PROPERTY xo_deps + BRIEF_DOCS "transitive completion of xo-dependencies for a target. Used with XO_SUBMODULE_BUILD" + ) + define_property( + TARGET + PROPERTY xo_srcdir + BRIEF_DOCS "snapshot of PROJECT_SOURCE_DIR asof when this target introduced" + ) + define_property( + TARGET + PROPERTY xo_bindir + BRIEF_DOCS "snapshot of PROJECT_BINARY_DIR asof when this target introduced" + ) + + if(NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) + endif() + if(NOT DEFINED CMAKE_CXX_STANDARD_REQUIRED) + set(CMAKE_CXX_STANDARD_REQUIRED True) + endif() + + if(NOT DEFINED PROJECT_INCLUDE_STEM_DIR) + set(PROJECT_INCLUDE_STEM_DIR xo) + endif() + + # ---------------------------------------------------------------- + # 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() + + set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra) + # XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON + # + # address sanitizer build complains about _FORTIFY_SOURCE redefines + # In file included from :460: + # :1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined] + # #define _FORTIFY_SOURCE 2 + # + set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined) + + if(XO_ADDRESS_SANITIZE) + set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS}) + else() + set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS}) + endif() + + # writes ${PROJECT_BINARY_DIR}/compile_commands.json; + # (symlink from toplevel git dir to tell LSP how to build) + # + # note: trying to protect this with if(NOT DEFINED ..) is /not/ effective + # + set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "") + + if(NOT CMAKE_INSTALL_RPATH) + set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib CACHE STRING + "runpath in installed libraries/executables") + endif() +endmacro() + +# xo_strip_xo_prefix(xo_foo tmp) --> tmp=foo +# xo_strip_xo_prefix(xo-foo tmp) --> tmp=foo +# xo_strip_xo_prefix(foo tmp) --> tmp=foo +# +macro(xo_strip_xo_prefix str outputvar) + string(REGEX REPLACE "^${PROJECT_INCLUDE_STEM_DIR}_" "" _tmp ${str}) + string(REGEX REPLACE "^${PROJECT_INCLUDE_STEM_DIR}-" "" ${outputvar} ${_tmp}) +endmacro() + +# e.g. +# - xo_target = xo_pyutil +# +macro(xo_include_headeronly_options target) + xo_strip_xo_prefix(${target} _nxo_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 + # + # NOTE: using INTERFACE here is mandatory. Otherwise get error: + # target_include_directories may only set INTERFACE properties on INTERFACE targets + # + target_include_directories( + ${target} INTERFACE + $ + $ + $ # e.g. for #include "indentlog/scope.hpp" + $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect + $ # e.g. for generated .hpp files + ) + + # ---------------------------------------------------------------- + # 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() + +macro(xo_include_headeronly_options2 target) + xo_include_headeronly_options(${target}) +endmacro() + +# ---------------------------------------------------------------- +# use this to introduce a shared library. +# - has symlink-enabled .hpp install +# +macro(xo_add_shared_library4 target projectTargets targetversion soversion sources) + add_library(${target} SHARED ${sources}) + + set_property( + TARGET ${target} + PROPERTY xo_deps "${target}") + set_property( + TARGET ${target} + PROPERTY xo_srcdir ${PROJECT_SOURCE_DIR}) + set_property( + TARGET ${target} + PROPERTY xo_bindir ${PROJECT_BINARY_DIR}) + + foreach(arg IN ITEMS ${ARGN}) + #message("target=${target}; arg=${arg}") + + # to use PUBLIC here would need to split: + # $ + # $ + # but shouldn't need that, since we arrange to install includes via + # xo_include_options2() below + # + target_sources(${target} PRIVATE ${arg}) + endforeach() + + set_property( + TARGET all_libraries_${PROJECT_NAME} + APPEND + PROPERTY targets ${target}) + + set_target_properties( + ${target} + PROPERTIES + VERSION ${targetversion} + SOVERSION ${soversion}) + + xo_compile_options(${target}) + xo_include_options2(${target}) + xo_install_library4(${target} ${projectTargets}) +endmacro() + +# ---------------------------------------------------------------- +# OBSOLETE. prefer xo_add_shared_library4() +# +macro(xo_add_shared_library3 target projectTargets targetversion soversion sources) + message(WARNING "${target}: obsolete call to xo_add_shared_library3(); prefer xo_add_shared_library4()") + + add_library(${target} SHARED ${sources}) + + foreach(arg IN ITEMS ${ARGN}) + #message("target=${target}; arg=${arg}") + + # to use PUBLIC here would need to split: + # $ + # $ + # but shouldn't need that, since we arrange to install includes via + # xo_include_options2() below + # + target_sources(${target} PRIVATE ${arg}) + endforeach() + + set_property( + TARGET all_libraries_${PROJECT_NAME} + APPEND + PROPERTY targets ${target}) + + set_target_properties( + ${target} + PROPERTIES + VERSION ${targetversion} + SOVERSION ${soversion}) + xo_compile_options(${target}) + xo_include_options2(${target}) + xo_install_library3(${target} ${projectTargets}) +endmacro() + +# ---------------------------------------------------------------- +# OBSOLETE. prefer xo_add_shared_library3() +# +macro(xo_add_shared_library target targetversion soversion sources) + message(WARNING "${target}: obsolete call to xo_add_shared_library(); prefer xo_add_shared_library4()") + + add_library(${target} SHARED ${sources}) + + foreach(arg IN ITEMS ${ARGN}) + #message("target=${target}; arg=${arg}") + + # to use PUBLIC here would need to split: + # $ + # $ + # but shouldn't need that, since we arrange to install includes via + # xo_include_options2() below + # + target_sources(${target} PRIVATE ${arg}) + endforeach() + + set_property( + TARGET all_libraries_${PROJECT_NAME} + APPEND + PROPERTY targets ${target}) + + set_target_properties( + ${target} + PROPERTIES + VERSION ${targetversion} + SOVERSION ${soversion}) + xo_compile_options(${target}) + xo_include_options2(${target}) + xo_install_library2(${target}) +endmacro() + +# ---------------------------------------------------------------- +# use this for a header-only library +# +# e.g. +# - target=xo_pyutil cmake target name for this library +# +macro(xo_add_headeronly_library4 target projectTargets) + add_library(${target} INTERFACE) + + set_property( + TARGET all_libraries_${PROJECT_NAME} + APPEND + PROPERTY targets ${target}) + + set_property( + TARGET ${target} + PROPERTY xo_deps "${target}") + set_property( + TARGET ${target} + PROPERTY xo_srcdir ${PROJECT_SOURCE_DIR}) + set_property( + TARGET ${target} + PROPERTY xo_bindir ${PROJECT_BINARY_DIR}) + + xo_include_headeronly_options(${target}) + + xo_install_library4(${target} ${projectTargets}) +endmacro() + +macro(xo_add_headeronly_library target) + add_library(${target} INTERFACE) + + set_property( + TARGET all_libraries_${PROJECT_NAME} + APPEND + PROPERTY targets ${target}) + + set_property( + TARGET ${target} + PROPERTY xo_deps "${target}") + set_property( + TARGET ${target} + PROPERTY xo_srcdir ${PROJECT_SOURCE_DIR}) + set_property( + TARGET ${target} + PROPERTY xo_bindir ${PROJECT_BINARY_DIR}) + + xo_include_headeronly_options(${target}) +endmacro() + +# ---------------------------------------------------------------- +# use this for an executable +# +macro(xo_add_executable target sources) + # this only adds the first argument.. + add_executable(${target} ${sources}) + xo_include_options2(${target}) + + # this adds all the remaining arguments + foreach(arg IN ITEMS ${ARGN}) + target_sources(${target} PRIVATE ${arg}) + endforeach() + + # remember executable for certain downstream targets, + # such as xo_doxygen_collect_deps() + # + set_property( + TARGET all_executables_${PROJECT_NAME} + APPEND + PROPERTY targets ${target}) +endmacro() + +# ---------------------------------------------------------------- +# use this for a unit test executable +# +macro(xo_add_utest_executable target sources) + # this only adds the first argument.. + add_executable(${target} ${sources}) + xo_include_options2(${target}) + + add_test(NAME ${target} COMMAND ${target}) + + # this adds all the remaining arguments + foreach(arg IN ITEMS ${ARGN}) + target_sources(${target} PRIVATE ${arg}) + endforeach() + + set_property( + TARGET all_utest_executables_${PROJECT_NAME} + APPEND + PROPERTY targets ${target}) + +endmacro() + +# ---------------------------------------------------------------- +# +# in submodule build each xo codebases cmake files are incorporated +# directly (via add_subdirectory()) into a single umbrella build. +# +# In such case we don't use find_package for xo dependencies +# +macro(xo_establish_submodule_build) + if(NOT DEFINED XO_SUBMODULE_BUILD) + set(XO_SUBMODULE_BUILD False) + endif() +endmacro() + +# ---------------------------------------------------------------- +# use this in subdirs that compile c++ code. +# do not use for header-only subsystems; see xo_include_headeronly_options2() +# +macro(xo_include_options2 target) + xo_establish_submodule_build() + + xo_strip_xo_prefix(${target} _nxo_target) + + #message("xo_include_options2: XO_SUBMODULE_BUILD=${XO_SUBMODULE_BUILD}") + + # ---------------------------------------------------------------- + # PROJECT_SOURCE_DIR: + # so we can for example write + # #include "ordinaltree/foo.hpp" + # from anywhere in the project + # PROJECT_BINARY_DIR: + # since generated version file will be in build directory, + # need that build directory to also appear in + # compiler's include path + # + target_include_directories( + ${target} PUBLIC + $ + $ + $ # e.g. for #include "indentlog/scope.hpp" + #$ # e.g. for #include "Refcounted.hpp" in refcnt/src [DEPRECATED] + $ # e.g. for #include "TypeDescr.hpp" in reflect/src when ${target}=reflect + #$ # e.g. for generated .hpp files + $ # e.g. for generated .hpp files + ) + + # ---------------------------------------------------------------- + # 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() + +# ---------------------------------------------------------------- +# +# Require: +# needs to be preceded by call to xo_toplevel_compile_options() +# +macro(xo_compile_options target) + target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS}) +endmacro() + +# ---------------------------------------------------------------- +# +# establish default value for XO_SYMLINK_INSTALL. +# +# may want to use this for a nested build, +# where we want to run cmake for nested codebase using externalproject_add(). +# +# in this case: +# 1. will need to build+install nested project foo to temporary location in build tree, +# so that build artifacts (such as fooConfig.cmake) are available to depending +# projects +# 2. bona fide install with (for example) copied .hpp files interferes with +# cross-codebase development. +# 2a. want changes to original .hpp files to trigger rebuild of depending codebases. +# This won't happen if depending project relies on a copy +# 2b. want IDE that observes compiler commands (i.e. LSP) to visit .hpp files +# in their original codebase, since that's the correct place to make any edits. +# +# see +# - xo_install_include_tree() +# +macro(xo_establish_symlink_install) + if(NOT DEFINED XO_SYMLINK_INSTALL) + set(XO_SYMLINK_INSTALL False) + + #message(XO_SYMLINK_INSTALL=${XO_SYMLINK_INSTALL}) + endif() +endmacro() + +# ---------------------------------------------------------------- +# use this to install typical include file subtree +# +macro(xo_install_include_tree) + message(WARNING "deprecated xo_install_include_tree(); prefer xo_install_include_tree3()") + + xo_establish_symlink_install() + + if(XO_SYMLINK_INSTALL) + message(FATAL_ERROR "symlink install not implemented for ${PROJECT_SOURCE_DIR}/include -- use xo_install_include_tree3()") + else() + install( + DIRECTORY ${PROJECT_SOURCE_DIR}/include/ + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/include) + endif() +endmacro() + +# create symlink from ${symlink_path} -> ${dest_path}, +# from +# make install +# +macro(xo_install_make_symlink dest_path symlink_dir symlink_name) + install(CODE "message(\"make_directory: ${symlink_dir}\")") + install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${symlink_dir})") + + install(CODE "message(\"symlink: ${symlink_dir}/${symlink_name} -> ${dest_path}/${symlink_name}\")") + install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink ${dest_path}/${symlink_name} ${symlink_dir}/${symlink_name})") +endmacro() + +# e.g. path = include/xo/foo to install xo-foo/include/xo/foo +# +macro(xo_install_include_tree3 subdir_path) + xo_establish_symlink_install() + + # ugh. cmake doesn't allow input path argument to cmake_path() + # to be a macro variable. + set(_xo_install_include_tree3_subdir_path ${subdir_path}) + set(_xo_install_include_tree3_dirname "") + set(_xo_install_include_tree3_basename "") + cmake_path(GET _xo_install_include_tree3_subdir_path PARENT_PATH _xo_install_include_tree3_dirname) + cmake_path(GET _xo_install_include_tree3_subdir_path FILENAME _xo_install_include_tree3_basename) + + if(XO_SYMLINK_INSTALL) + + xo_install_make_symlink( + ${PROJECT_SOURCE_DIR}/${_xo_install_include_tree3_dirname} + ${CMAKE_INSTALL_PREFIX}/${_xo_install_include_tree3_dirname} + ${_xo_install_include_tree3_basename}) + else() + install( + DIRECTORY ${PROJECT_SOURCE_DIR}/${subdir_path} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/${_xo_install_include_tree3_dirname}) + endif() +endmacro() + +# ---------------------------------------------------------------- +# use this for a subdir that builds a library +# and supports find_package() +# +# note: used deliberately in xo_pybind11_library() below +# +macro(xo_install_library2 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() + +macro(xo_install_library3 target projectTargets) + install( + TARGETS ${target} + EXPORT ${projectTargets} + 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 + ) + + xo_install_include_tree() +endmacro() + +macro(xo_install_library4 target projectTargets) + xo_strip_xo_prefix(${target} _nxo_target) + + install( + TARGETS ${target} + EXPORT ${projectTargets} + 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 + ) + + xo_install_include_tree3(include/${PROJECT_INCLUDE_STEM_DIR}/${_nxo_target}) + + #xo_install_include_tree() -- use xo_install_include_tree3() separately +endmacro() + +macro(xo_install_library4_noincludes target projectTargets) + install( + TARGETS ${target} + EXPORT ${projectTargets} + 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() + +# ---------------------------------------------------------------- + +# for projectname=foo, require: +# cmake/fooConfig.cmake.in +# +# prepares +# ${PREFIX}/lib/cmake/foo/fooConfig.cmake +# ${PREFIX}/lib/cmake/foo/fooConfigVersion.cmake +# ${PREFIX}/lib/cmake/foo/fooTargets.cmake +# +macro(xo_export_cmake_config projectname projectversion projecttargets) + include(CMakePackageConfigHelpers) + write_basic_package_version_file( + "${PROJECT_BINARY_DIR}/${projectname}ConfigVersion.cmake" + VERSION ${projectversion} + COMPATIBILITY AnyNewerVersion + ) + configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/${projectname}Config.cmake.in" + "${PROJECT_BINARY_DIR}/${projectname}Config.cmake" + INSTALL_DESTINATION lib/cmake/${projectname} + ) + install( + EXPORT ${projecttargets} + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/${projectname}) + install( + FILES + "${PROJECT_BINARY_DIR}/${projectname}ConfigVersion.cmake" + "${PROJECT_BINARY_DIR}/${projectname}Config.cmake" + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/cmake/${projectname} + ) +endmacro() + +# ---------------------------------------------------------------- +# helper macro for xo_dependency_helper() see below +# +macro(xo_dependency_helper1 target visibility dep_include_subdir) + target_include_directories(${target} ${visibility} + $) + target_include_directories(${target} ${visibility} + $) + + # note: header directories owned by dep get added to target. + # however, header directories _used_ by dep don't get automatically added to target, + # (for example if referred to from a dep-owned .hpp file) + # +endmacro() + +# ---------------------------------------------------------------- +# dependency helper when depending on an xo codebase +# +# Two flavors: +# - depended-on codebase separately built+installed. +# In this case behaves like any other best-practice cmake dep: +# use find_package() to rely on install .cmake config files +# in PREFIX/lib/cmake +# - depended-on codebase in same umbrella source tree as codebase +# invoking this helper. +# 1. XO_UMBRELLA_SOURCE_DIR gives top of umbrella source tree +# 2. depended-on codebase foo in one of +# XO_UMBRELLA_SOURCE_DIR/repo/{foo, xo-foo} +# +# target: cmake target for which to supply a dependency +# visibility: INTERFACE|PUBLIC +# - INTERFACE must be used when supplying a dependency to a header-only target +# dep: cmake target on which to depend (e.g. xo_pyutil) +# nxo_dep: cmake target without any xo_ prefix. (e.g. pyutil) +# +macro(xo_dependency_helper target visibility dep) + xo_strip_xo_prefix(${dep} _nxo_dep) + + xo_establish_submodule_build() + + if(XO_SUBMODULE_BUILD) + if(EXISTS ${XO_UMBRELLA_SOURCE_DIR}/${XO_UMBRELLA_REPO_SUBDIR}/xo-${_nxo_dep}) + xo_dependency_helper1(${target} ${visibility} ${XO_UMBRELLA_REPO_SUBDIR}/xo-${_nxo_dep}/include) + endif() + + if(EXISTS ${XO_UMBRELLA_SOURCE_DIR}/${XO_UMBRELLA_REPO_SUBDIR}/${_nxo_dep}) + xo_dependency_helper1(${target} ${visibility} ${XO_UMBRELLA_REPO_SUBDIR}/${_nxo_dep}/include) + endif() + + else() + message(STATUS "[${target}] find_package(${dep}) (xo_dependency_helper)") + find_package(${dep} CONFIG REQUIRED) + endif() + + if (XO_SUBMODULE_BUILD) + #get_target_property(_tmp ${target} LINK_LIBRARIES) + #message("xo_dependency_helper: ${target} -> ${dep}: ${target}.LINK_LIBRARIES=${_tmp}") + #get_target_property(_tmp ${dep} LINK_LIBRARIES) + #message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.LINK_LIBRARIES=${_tmp}") + + #get_target_property(_tmp ${target} INTERFACE_LINK_LIBRARIES) + #message("xo_dependency_helper: ${target} -> ${dep}: ${target}.INTERFACE_LINK_LIBRARIES=${_tmp}") + #get_target_property(_tmp ${dep} INTERFACE_LINK_LIBRARIES) + #message("xo_dependency_helper: ${target} -> ${dep}: ${dep}.INTERFACE_LINK_LIBRARIES=${_tmp}") + + # add INCLUDE_DIRECTORIES from ${dep} to ${target}. + # + # 1. we only need this for a submodule build + # 1a. cmake automatically adds ${dep}'s directly-set include directories + # (i.e. attached via target_include_directories(${dep} ..) etc. + # 1b. furthermore, cmake adds transitive deps when we use generated + # cmake support + # 1c. cmake doesn't seem automatically to arrange transitive include directories + # when we have a chain of target_link_libraries() calls. + # (NOTE: it does incorporate include paths from direct dependencies) + # 2. because of 1a, need to exclude ${dep}'s own dirs here (to avoid too-long include path) + # + get_target_property(_depsrcdir ${dep} xo_srcdir) + get_target_property(_depbindir ${dep} xo_bindir) + get_target_property(_tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) + if(_tmp) + #message("xo_dependency_helper: ${target}: + ${dep}.INCLUDE_DIRECTORIES: ${_tmp}") + foreach(dir ${_tmp}) + # want to add these to compile line for $target}. + # this will happen automatically for ${dep}'s own directories; + # those will have been added by + # xo_include_options2() / xo_include_headeronly_options2() + # however we also need directories for ${dep}'s transitive dependencies + # + if(${dir} MATCHES "BUILD_INTERFACE") + #message("xo_dependency_helper: ${target} -> ${dep}: consider dir=${dir}") + if(${dir} MATCHES ${_depsrcdir}) + #message(" skip ${dir}") + elseif(${dir} MATCHES ${_depbindir}) + #message(" skip ${dir}") + else() + #message(" KEEP ${dir}") + target_include_directories(${target} ${visibility} ${dir}) + endif() + endif() + endforeach() + get_target_property(_tmp ${target} INTERFACE_INCLUDE_DIRECTORIES) + list(REMOVE_DUPLICATES _tmp) + set_property( + TARGET ${target} + PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${_tmp}) + #message("xo_dependency_helper: ${target}.INCLUDE_DIRECTORIES: ${_tmp}") + endif() + endif() + + # want ${target}.xo_deps to contain union of previous value, and ${dep}.xo_deps + set_property( + TARGET ${target} + APPEND + PROPERTY xo_deps ${dep}) + get_target_property(_tmp ${target} xo_deps) + list(REMOVE_DUPLICATES _tmp) + set_property( + TARGET ${target} + PROPERTY xo_deps ${_tmp}) + + #list(REMOVE_DUPLICATES _xo_dependency_tmp1) +endmacro() + +# ---------------------------------------------------------------- +# +# dependency on an xo library (including header-only libraries) +# e.g. indentlog +# +# An xo package foo works with cmake +# find_package(foo) +# by providing plugin .cmake files in +# ${PREFIX}/lib/cmake/foo/fooConfig.cmake +# ${PREFIX}/lib/cmake/foo/fooConfigVersion.cmake +# ${PREFIX}/lib/cmake/foo/fooTargets.cmake +# +# dep: name of required dependency, e.g. indentlog +# +macro(xo_dependency target dep) + xo_establish_submodule_build() + + #message("xo_dependency: XO_SUBMODULE_BUILD=${XO_SUBMODULE_BUILD}") + #message("xo_dependency: XO_UMBRELLA_SOURCE_DIR=${XO_UMBRELLA_SOURCE_DIR}") + #message("xo_dependency: PROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}") + + xo_dependency_helper(${target} PUBLIC ${dep}) + + #message("----------------------------------------------------------------") + ##message("xo_dependency: ${target}.xo_deps.pre=${_xo_dependency_tmp0}") + ##message("xo_dependency: ${dep}.xo_deps=${_xo_dependency_tmp2}") + #get_target_property(_tmp ${target} xo_deps) + #message("xo_dependency: ${target}.xo_deps=${_tmp}") + ##get_target_property(_tmp ${target} INCLUDE_DIRECTORIES) + ##message("xo_dependency: ${target}.INCLUDE_DIRECTORIES=${_tmp} before target_link_libraries with ${dep}") + + target_link_libraries(${target} PUBLIC ${dep}) + #target_link_libraries(${target} ${dep}) + + #get_target_property(_tmp ${target} INCLUDE_DIRECTORIES) + #message("xo_dependency: ${target}.INCLUDE_DIRECTORIES=${_tmp} after target_link_libraries with ${dep}") + #get_target_property(_tmp ${dep} INCLUDE_DIRECTORIES) + #message("xo_dependency: ${dep}.INCLUDE_DIRECTORIES=${_tmp}") + #get_target_property(_tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) + #message("xo_dependency: ${dep}.INTERFACE_INCLUDE_DIRECTORIES=${_tmp}") + #message("----------------------------------------------------------------") +endmacro() + +# dependency of a header-only library on another header-only library +# +macro(xo_headeronly_dependency target dep) + xo_establish_submodule_build() + + xo_dependency_helper(${target} INTERFACE ${dep}) + + # Conflict here between PUBLIC and INTERFACE + # + # PUBLIC ensures that include directories required by ${dep} will also be included in compilation of ${target}; + # i.e. will appear in property ${target}.INCLUDE_DIRECTORIES + # + # INTERFACE mandatory when depending on a header-only library (created with add_library(foo INTERFACE)). + # otherwise get error: + # INTERFACE library can only be used with the INTERFACE keyword of + # target_link_libraries + # Unfortunately target_link_libraries() does not copy dependent's INTERFACE_INCLUDE_DIRECTORIES property + # (at least asof cmake 3.25.3). Dependent's INCLUDE_DIRECTORIES property will be empty, since it's header-only. + # + # Workaround by copying property explicity, which we do below + # + target_link_libraries(${target} INTERFACE ${dep}) + + # get_target_property(xo_dependency_headeronly__tmp ${dep} INTERFACE_INCLUDE_DIRECTORIES) + # set_property( + # TARGET ${target} + # APPEND PROPERTY INCLUDE_DIRECTORIES ${xo_dependency_headeronly__tmp}) +endmacro() + +# dependency on external (non-xo) namespaced target +# e.g. +# add_library(foo ..) or add_executable(foo ...) +# then +# xo_external_namespaced_dependency(foo Catch2 Catch2::Catch2) +# equivalent to +# find_package(Catch2 CONFIG REQUIRED) +# target_link_libraries(foo PUBLIC Catch2::Catch2) +# +macro(xo_external_target_dependency target pkg pkgtarget) + message("-- [${target}] find_package(${pkg}) (xo_external_target_dependency)") + find_package(${pkg} CONFIG REQUIRED) + target_link_libraries(${target} PUBLIC ${pkgtarget}) + #target_link_libraries(${target} ${pkgtarget}) +endmacro() + +# dependency on external (non-xo) target +# +macro(xo_external_dependency target pkg) + xo_external_target_dependency(${target} ${pkg} ${target}) +endmacro() + +# dependency on target provided from this codebase. +# +# 1. don't need find_package() in this case, since details of dep targets +# must be known to cmake for it to build them. +# 2. in any case, can't use find_package() when cmake runs, +# because supporting .cmake files haven't been generated yet +# +macro(xo_self_dependency target dep) + target_link_libraries(${target} PUBLIC ${dep}) +endmacro() + +# dependency on target provided from this codebase. +# +# Similar comments as for xo_self_dependency() +# 1. don't need find_package() in this case, since details of dep targets +# must be known to cmake for it to build them. +# 2. in any case, can't use find_package() when cmake runs, +# because supporting .cmake files haven't been generated yet +# 3. need to use INTERFACE instead of PUBLIC for a header-only dep +# +macro(xo_self_headeronly_dependency target dep) + target_link_libraries(${target} INTERFACE ${dep}) +endmacro() + +# ---------------------------------------------------------------- +# need this when linking pybind11-generated libraries +# +macro(xo_pybind11_link_flags) + # see: + # 1. FAQ Build Issues Q2 + # 2. CMAKE_SHARED_LINKER_FLAGS in src/CMakeLists.txt + # 3. pybind11 cmake support, somewhere like + # [path/to/pybind11-2.9.2/ + # lib/python3.9-pybind11-2.9.2/ + # lib/python3.9/site-packages/ + # pybind11/share/cmake/ + # pybind11/pybind11Common.cmake] + # + set_property( + TARGET pybind11::python_link_helper + APPEND + PROPERTY + INTERFACE_LINK_OPTIONS "$<$:LINKER:-flat_namespace>") + + # looks like pybind11_add_module() tries to link transitive deps + # of libs mentioned in xo_dependency() -- perhaps for link-time optimization? + # + # For example when linking libpyreflect, get link line with -lrefcnt + # (presumably since cmake knows that libreflect.so depends on librefcnt.so); + # this triggers error, since link doesn't know where to find librefcnt.so + # + # To workaround, add ${CMAKE_INSTALL_PREFIX}/lib to the link line. + # + # WARNING: expect this not to work in a hermetic nix build! + # + set_property(TARGET pybind11::python_link_helper + APPEND + PROPERTY + INTERFACE_LINK_OPTIONS "-L${CMAKE_INSTALL_PREFIX}/lib") +endmacro() + +# ---------------------------------------------------------------- +# use this for a subdir that builds a python library using pybind11 +# +# expecting the following +# 1. a directory pyfoo/ -> library pyfoo +# 2. pyfoo/pyfoo.hpp.in -> pyfoo/pyfoo.hpp +# +macro(xo_pybind11_library target projectTargets source_files) + xo_strip_xo_prefix(${target} _nxo_target) + + file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/include/${PROJECT_INCLUDE_STEM_DIR}/${_nxo_target}) + + configure_file( + ${_nxo_target}.hpp.in + ${PROJECT_BINARY_DIR}/include/${PROJECT_INCLUDE_STEM_DIR}/${_nxo_target}/${_nxo_target}.hpp) + # was ${PROJECT_SOURCE_DIR}/include/xo/${target}/${target}.hpp) + + xo_establish_symlink_install() + + if(XO_SYMLINK_INSTALL) + xo_install_make_symlink( + ${PROJECT_BINARY_DIR}/include/xo + ${CMAKE_INSTALL_PREFIX}/include/xo + ${_nxo_target}) + else() + install( + FILES ${PROJECT_BINARY_DIR}/include/${PROJECT_INCLUDE_STEM_DIR}/${_nxo_target}/${_nxo_target}.hpp + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/${PROJECT_INCLUDE_STEM_DIR}/${_nxo_target}) + endif() + + message(STATUS "[${target}] find_package(Python) (xo_pybind11_library)") + find_package(Python COMPONENTS Interpreter Development REQUIRED) + message(STATUS "[${target}] find_package(pybind11) (xo_pybind11_library)") + find_package(pybind11) + + # this only works if one source file, right? + # + # 6oct2023 + # Having trouble at link time with this. + # Getting broken link for nix, because link line lists short library nicknames + # (e.g. -lfoo) for transitive deps. nix link needs to be given the directory + # in which libfoo.so resides, so we need to ensure full path + # + # - source files: + # -fPIC -fvisibility=hidden -flto -fno-fat-lto-objects + # - library: + # -fPIC -flto + # (transitive closure of library deps for lto?) + # + pybind11_add_module(${target} MODULE ${source_files}) + + set_property( + TARGET all_libraries_${PROJECT_NAME} + APPEND + PROPERTY targets ${target}) + + set_property( + TARGET ${target} + PROPERTY xo_deps "${target}") + set_property( + TARGET ${target} + PROPERTY xo_srcdir ${PROJECT_SOURCE_DIR}) + set_property( + TARGET ${target} + PROPERTY xo_bindir ${PROJECT_BINARY_DIR}) + + xo_pybind11_link_flags() + xo_include_options2(${target}) + # don't want to symlink include tree, because lives in build dir. + # see install for generated .hpp above + xo_install_library4_noincludes(${target} ${projectTargets}) +endmacro() + +# ---------------------------------------------------------------- +# use this for a dependency of a pybind11 library, +# e.g. that was introduced by xo_pybind11_library() +# +# Working around the following problem (cmake 3.25.3, pybind11 2.10.4) +# if: +# 1. we have pybind11 library pyfoo, depending on c++ native library foo. +# 2. foo depends on other libraries foodep1, foodep2; +# assume also that foodep2 is header-only +# +# if we write: +# # CMakeLists.txt +# pybind11_add_module(pyfoo MODULE pyfoo.cpp) +# find_package(foo CONFIG_REQUIRED) +# target_link_libraries(pyfoo PUBLIC foo) +# +# get compile instructions like: +# g++ -o pyfoo.cpython-311-x86_64-linux-gnu.so path/to/pyfoo.cpp.o path/to/libfoo.so.x.y -lfoodep1 -lfoodep2 +# +# 1. This is broken for foodep2, since in this case no libfoodep2.so exists +# 2. Also broken for nix build, because directory containing libfoodep1.so doesn't appear on the compile line. +# (It's likely possible to extract this from the .cmake package in lib/cmake/foo/fooTargets.cmake, +# but I don't know how to do that yet) +# +# workaround here is to suppress these secondary dependencies. +# This assumes: +# 1. secondary dependencies are all in shared libraries (not needed on link line) +# 2. (maybe?) primary dependency libfoo.so is sufficient to satisfy g++ +# -- conceivably true if libfoo.so has RUNPATH etc. +# +macro(xo_pybind11_dependency target dep) + xo_establish_submodule_build() + + xo_dependency_helper(${target} PUBLIC ${dep}) + # clobber secondary dependencies, as discussed above + if(XO_SUBMODULE_BUILD) + # ok to keep dep libraries on link line in submodule build + #message("xo_pybind11_dependency: ${target}: don't clobber ${dep}.INTERFACE_LINK_LIBRARIES") + + # looks like also broken in submodule build? + #message("-- [${target}] remove ${dep}.INTERFACE_LINK_LIBRARIES to avoid problems with transitive deps (xo_pybind11_dependency)") + #set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") + else() + message("-- [${target}] remove ${dep}.INTERFACE_LINK_LIBRARIES to avoid problems with transitive deps (xo_pybind11_dependency)") + set_property(TARGET ${dep} PROPERTY INTERFACE_LINK_LIBRARIES "") + + # also have to clobber libraries for + endif() + # now that secondary deps are gone, attach to target pybind11 library + # skip xo_dependency() here, that would repeat the find_package() expansion + target_link_libraries(${target} PUBLIC ${dep}) +endmacro() + +# use when one xo pybind library imports another. +# for example +# pyprintjson -> pyreflect +# +macro(xo_pybind11_header_dependency target dep) + xo_dependency_helper(${target} PUBLIC ${dep}) +endmacro() diff --git a/xo-indentlog/xo-cmake/etc/xo/subsystem-list b/xo-indentlog/xo-cmake/etc/xo/subsystem-list new file mode 100644 index 00000000..d1dcf6c7 --- /dev/null +++ b/xo-indentlog/xo-cmake/etc/xo/subsystem-list @@ -0,0 +1,38 @@ +xo-cmake +xo-indentlog +xo-refcnt +xo-subsys +xo-randomgen +xo-ordinaltree +xo-pyutil +xo-flatstring +xo-reflectutil +xo-reflect +xo-pyreflect +xo-ratio +xo-unit +xo-pyunit +xo-expression +xo-pyexpression +xo-tokenizer +xo-reader +xo-jit +xo-pyjit +xo-callback +xo-webutil +xo-pywebutil +xo-printjson +xo-pyprintjson +xo-reactor +xo-pyreactor +xo-websock +xo-pywebsock +xo-statistics +xo-distribution +xo-pydistribution +xo-simulator +xo-pysimulator +xo-process +xo-pyprocess +xo-kalmanfilter +xo-pykalmanfilter diff --git a/xo-indentlog/xo-cmake/share/xo-macros/Doxyfile.in b/xo-indentlog/xo-cmake/share/xo-macros/Doxyfile.in new file mode 100644 index 00000000..13dfb0fa --- /dev/null +++ b/xo-indentlog/xo-cmake/share/xo-macros/Doxyfile.in @@ -0,0 +1,2816 @@ +# If filename is Doxyfile.in: +# template (to be expanded by cmake) for real doxyfile +# @SOMEVAR@ expands to value of cmake variable SOMEVAR +# +# expressions to be expanded include: +# @DOX_INPUT_DIR@ +# @DOX_OUTPUT_DIR@ +# +# if filename is Doxyfile: +# expanded template in build directory, to configure doxygen + +# Doxyfile 1.9.7 +# + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Cmake Examples" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @DOX_OUTPUT_DIR@ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = @DOX_INPUT_DIR@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN Use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0. and GITHUB Use the lower case version of title +# with any whitespace replaced by '-' and punctations characters removed.. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = YES + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = YES + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = YES + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = SYSTEM + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about +# undocumented enumeration values. If set to NO, doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @DOX_INPUT_DIR@ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding +# "INPUT_ENCODING" for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = */utest/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generate light mode output, DARK always +# generate dark mode output, AUTO_LIGHT automatically set the mode according to +# the user preference, use light mode if no preference is set (the default), +# AUTO_DARK automatically set the mode according to the user preference, use +# dark mode if no preference is set and TOGGLE allow to user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /